GoodBuddy previously exposed Runtime capabilities, MCP assignments, plugin controls, context compaction, usage reporting, and task-completion behavior through incomplete or inconsistent paths. This release unifies managed OpenCode, Continue, and DeepSeek Harness controls; adds DSH plugin and image workflows; strengthens MCP and Runtime lifecycle bounds; fixes Windows notification activation; validates cross-architecture packages; and presents the approved bilingual four-section release notes. The DSH plugin marketplace remains a default-off preview whose trusted third-party code runs with current-user permissions. Ask remains read-only, Execute keeps approval controls, and context compaction may use the selected model without deleting GoodBuddy chat history. Release note: GoodBuddy 0.10.0 重点完善多 Runtime 工作流,统一 OpenCode、Continue 与 DeepSeek Harness 的能力、MCP、插件和上下文管理,并提升长对话、多会话与任务通知的连贯性。
195 lines
5.1 KiB
TypeScript
195 lines
5.1 KiB
TypeScript
import {
|
|
mkdtemp,
|
|
rm,
|
|
writeFile
|
|
} from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import type { ShortcutDetails } from 'electron'
|
|
import {
|
|
afterEach,
|
|
describe,
|
|
expect,
|
|
it,
|
|
vi
|
|
} from 'vitest'
|
|
import {
|
|
isInstalledWindowsBuild,
|
|
repairStaleWindowsNotificationShortcuts,
|
|
resolveWindowsAppUserModelId,
|
|
WINDOWS_APP_USER_MODEL_ID
|
|
} from './windows-notification-identity'
|
|
|
|
const temporaryDirectories: string[] = []
|
|
|
|
async function createTemporaryDirectory(): Promise<string> {
|
|
const directory = await mkdtemp(
|
|
join(tmpdir(), 'goodbuddy-notification-identity-')
|
|
)
|
|
temporaryDirectories.push(directory)
|
|
return directory
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
temporaryDirectories
|
|
.splice(0)
|
|
.map((directory) =>
|
|
rm(directory, { recursive: true, force: true })
|
|
)
|
|
)
|
|
})
|
|
|
|
describe('Windows notification identity', () => {
|
|
it('reserves the production AUMID for an installed build', () => {
|
|
expect(
|
|
resolveWindowsAppUserModelId({
|
|
installed: true,
|
|
executablePath: 'C:\\Program Files\\GoodBuddy\\GoodBuddy.exe'
|
|
})
|
|
).toBe(WINDOWS_APP_USER_MODEL_ID)
|
|
})
|
|
|
|
it('uses stable path-scoped identities for standalone builds', () => {
|
|
const first = resolveWindowsAppUserModelId({
|
|
installed: false,
|
|
executablePath: 'D:\\repo\\GoodBuddy.exe'
|
|
})
|
|
|
|
expect(first).not.toBe(WINDOWS_APP_USER_MODEL_ID)
|
|
expect(
|
|
resolveWindowsAppUserModelId({
|
|
installed: false,
|
|
executablePath: 'd:\\REPO\\goodbuddy.exe'
|
|
})
|
|
).toBe(first)
|
|
expect(
|
|
resolveWindowsAppUserModelId({
|
|
installed: false,
|
|
executablePath: 'D:\\other\\GoodBuddy.exe'
|
|
})
|
|
).not.toBe(first)
|
|
})
|
|
|
|
it('recognizes only packaged Windows layouts with an uninstaller', async () => {
|
|
const directory = await createTemporaryDirectory()
|
|
const executablePath = join(directory, 'GoodBuddy.exe')
|
|
await writeFile(join(directory, 'Uninstall GoodBuddy.exe'), '')
|
|
|
|
expect(
|
|
isInstalledWindowsBuild({
|
|
packaged: true,
|
|
platform: 'win32',
|
|
executablePath
|
|
})
|
|
).toBe(true)
|
|
expect(
|
|
isInstalledWindowsBuild({
|
|
packaged: false,
|
|
platform: 'win32',
|
|
executablePath
|
|
})
|
|
).toBe(false)
|
|
expect(
|
|
isInstalledWindowsBuild({
|
|
packaged: true,
|
|
platform: 'linux',
|
|
executablePath
|
|
})
|
|
).toBe(false)
|
|
expect(
|
|
isInstalledWindowsBuild({
|
|
packaged: true,
|
|
platform: 'win32',
|
|
executablePath: '\0'
|
|
})
|
|
).toBe(false)
|
|
})
|
|
|
|
it('moves stale shortcuts off the production identity without changing their targets', async () => {
|
|
const programsDirectory = await createTemporaryDirectory()
|
|
const stalePath = join(programsDirectory, 'Electron.lnk')
|
|
const currentPath = join(programsDirectory, 'GoodBuddy.lnk')
|
|
await Promise.all([
|
|
writeFile(stalePath, ''),
|
|
writeFile(currentPath, '')
|
|
])
|
|
|
|
const staleDetails: ShortcutDetails = {
|
|
target: 'D:\\repo\\node_modules\\electron\\electron.exe',
|
|
appUserModelId: WINDOWS_APP_USER_MODEL_ID,
|
|
toastActivatorClsid:
|
|
'{6D4C974B-001A-47E5-AE4B-F8F12FFDA281}'
|
|
}
|
|
const details = new Map<string, ShortcutDetails>([
|
|
[stalePath, staleDetails],
|
|
[
|
|
currentPath,
|
|
{
|
|
target:
|
|
'C:\\Program Files\\GoodBuddy\\GoodBuddy.exe',
|
|
appUserModelId: WINDOWS_APP_USER_MODEL_ID
|
|
}
|
|
]
|
|
])
|
|
const writeShortcutLink = vi.fn(() => true)
|
|
|
|
await expect(
|
|
repairStaleWindowsNotificationShortcuts({
|
|
platform: 'win32',
|
|
installed: true,
|
|
executablePath:
|
|
'C:\\Program Files\\GoodBuddy\\GoodBuddy.exe',
|
|
programsDirectory,
|
|
shortcutAccess: {
|
|
readShortcutLink: (shortcutPath) =>
|
|
details.get(shortcutPath)!,
|
|
writeShortcutLink
|
|
}
|
|
})
|
|
).resolves.toEqual({
|
|
scanned: 2,
|
|
repaired: 1,
|
|
failed: 0
|
|
})
|
|
expect(writeShortcutLink).toHaveBeenCalledOnce()
|
|
expect(writeShortcutLink).toHaveBeenCalledWith(
|
|
stalePath,
|
|
'update',
|
|
expect.objectContaining({
|
|
target: staleDetails.target,
|
|
toastActivatorClsid: staleDetails.toastActivatorClsid,
|
|
appUserModelId: expect.not.stringMatching(
|
|
/^live\.digiman\.goodbuddy$/u
|
|
)
|
|
})
|
|
)
|
|
})
|
|
|
|
it('does not touch shortcuts from development or portable builds', async () => {
|
|
const programsDirectory = await createTemporaryDirectory()
|
|
await writeFile(join(programsDirectory, 'Electron.lnk'), '')
|
|
const readShortcutLink = vi.fn()
|
|
const writeShortcutLink = vi.fn()
|
|
|
|
await expect(
|
|
repairStaleWindowsNotificationShortcuts({
|
|
platform: 'win32',
|
|
installed: false,
|
|
executablePath: 'D:\\repo\\GoodBuddy.exe',
|
|
programsDirectory,
|
|
shortcutAccess: {
|
|
readShortcutLink,
|
|
writeShortcutLink
|
|
}
|
|
})
|
|
).resolves.toEqual({
|
|
scanned: 0,
|
|
repaired: 0,
|
|
failed: 0
|
|
})
|
|
expect(readShortcutLink).not.toHaveBeenCalled()
|
|
expect(writeShortcutLink).not.toHaveBeenCalled()
|
|
})
|
|
})
|