feat: expand multi-runtime workflows for 0.10.0
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、插件和上下文管理,并提升长对话、多会话与任务通知的连贯性。
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { statSync } from 'node:fs'
|
||||
import { dirname, join, resolve, win32 } from 'node:path'
|
||||
import type { ShortcutDetails } from 'electron'
|
||||
|
||||
export const WINDOWS_APP_USER_MODEL_ID = 'live.digiman.goodbuddy'
|
||||
|
||||
const windowsUninstallerName = 'Uninstall GoodBuddy.exe'
|
||||
const standaloneIdentityPrefix = `${WINDOWS_APP_USER_MODEL_ID}.standalone`
|
||||
const knownShortcutNames = ['Electron.lnk', 'GoodBuddy.lnk'] as const
|
||||
|
||||
interface ShortcutAccess {
|
||||
readShortcutLink(shortcutPath: string): ShortcutDetails
|
||||
writeShortcutLink(
|
||||
shortcutPath: string,
|
||||
operation: 'update',
|
||||
options: ShortcutDetails
|
||||
): boolean
|
||||
}
|
||||
|
||||
export interface WindowsNotificationShortcutRepairResult {
|
||||
scanned: number
|
||||
repaired: number
|
||||
failed: number
|
||||
}
|
||||
|
||||
function normalizeWindowsExecutablePath(executablePath: string): string {
|
||||
return win32.resolve(executablePath).toLocaleLowerCase('en-US')
|
||||
}
|
||||
|
||||
function resolveStandaloneWindowsAppUserModelId(
|
||||
executablePath: string
|
||||
): string {
|
||||
const executableHash = createHash('sha256')
|
||||
.update(normalizeWindowsExecutablePath(executablePath))
|
||||
.digest('hex')
|
||||
.slice(0, 24)
|
||||
return `${standaloneIdentityPrefix}.${executableHash}`
|
||||
}
|
||||
|
||||
export function isInstalledWindowsBuild(input: {
|
||||
packaged: boolean
|
||||
platform: NodeJS.Platform
|
||||
executablePath: string
|
||||
}): boolean {
|
||||
if (!input.packaged || input.platform !== 'win32') {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
const uninstallerPath = join(
|
||||
dirname(resolve(input.executablePath)),
|
||||
windowsUninstallerName
|
||||
)
|
||||
return statSync(uninstallerPath, {
|
||||
throwIfNoEntry: false
|
||||
})?.isFile() === true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveWindowsAppUserModelId(input: {
|
||||
installed: boolean
|
||||
executablePath: string
|
||||
}): string {
|
||||
return input.installed
|
||||
? WINDOWS_APP_USER_MODEL_ID
|
||||
: resolveStandaloneWindowsAppUserModelId(input.executablePath)
|
||||
}
|
||||
|
||||
export async function repairStaleWindowsNotificationShortcuts(input: {
|
||||
platform: NodeJS.Platform
|
||||
installed: boolean
|
||||
executablePath: string
|
||||
programsDirectory: string
|
||||
shortcutAccess: ShortcutAccess
|
||||
}): Promise<WindowsNotificationShortcutRepairResult> {
|
||||
if (input.platform !== 'win32' || !input.installed) {
|
||||
return { scanned: 0, repaired: 0, failed: 0 }
|
||||
}
|
||||
|
||||
const shortcutPaths = knownShortcutNames.map((shortcutName) =>
|
||||
join(input.programsDirectory, shortcutName)
|
||||
)
|
||||
const currentExecutablePath = normalizeWindowsExecutablePath(
|
||||
input.executablePath
|
||||
)
|
||||
let scanned = 0
|
||||
let repaired = 0
|
||||
let failed = 0
|
||||
|
||||
for (const shortcutPath of shortcutPaths) {
|
||||
let details: ShortcutDetails
|
||||
try {
|
||||
details = input.shortcutAccess.readShortcutLink(shortcutPath)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
scanned += 1
|
||||
if (
|
||||
details.appUserModelId !== WINDOWS_APP_USER_MODEL_ID ||
|
||||
normalizeWindowsExecutablePath(details.target) ===
|
||||
currentExecutablePath
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const updated = input.shortcutAccess.writeShortcutLink(
|
||||
shortcutPath,
|
||||
'update',
|
||||
{
|
||||
...details,
|
||||
appUserModelId:
|
||||
resolveStandaloneWindowsAppUserModelId(details.target)
|
||||
}
|
||||
)
|
||||
if (updated) {
|
||||
repaired += 1
|
||||
} else {
|
||||
failed += 1
|
||||
}
|
||||
} catch {
|
||||
failed += 1
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
scanned,
|
||||
repaired,
|
||||
failed
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user