Skip to content

Commit 1a77f70

Browse files
author
JooHyung Park
committed
[ai-assisted] feat: switch assistant to bundled runtime and model installer flow
1 parent 7478516 commit 1a77f70

22 files changed

Lines changed: 2683 additions & 677 deletions

.github/workflows/build.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ jobs:
6666
- name: Install dependencies
6767
run: npm ci --prefer-offline
6868

69+
- name: Prepare bundled llama runtime (macOS)
70+
run: node scripts/prepare-llama-runtime.mjs --refresh --strict --targets=darwin-arm64,darwin-x64
71+
6972
- name: Verify required environment variables
7073
run: |
7174
if [ -z "${{ vars.APPLE_ID }}" ] || [ -z "${{ secrets.APPLE_PASSWORD }}" ] || [ -z "${{ secrets.APPLE_TEAM_ID }}" ] || [ -z "${{ secrets.SIGNING_IDENTITY }}" ]; then
@@ -213,6 +216,9 @@ jobs:
213216
- name: Install dependencies
214217
run: npm ci --prefer-offline
215218

219+
- name: Prepare bundled llama runtime (Windows)
220+
run: node scripts/prepare-llama-runtime.mjs --refresh --strict --targets=windows-x64
221+
216222
- name: Verify environment variables
217223
shell: bash
218224
run: |

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ Thumbs.db
3434
.npm
3535
.eslintcache
3636

37+
# Bundled llama runtime binaries (generated)
38+
runtime/llama/bin/*
39+
!runtime/llama/bin/.gitkeep
40+
3741
# Apple Developer certificates and profiles
3842
*.p12
3943
*.pem

forge.config.ts

Lines changed: 108 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const config: ForgeConfig = {
2929
icon: './public/icon', // Electron Forge will append .icns, .ico, .png automatically
3030
extraResource: [
3131
'./public',
32+
'./runtime/llama',
3233
],
3334
// Code signing configuration (uses .env locally, CI environment variables in automation)
3435
osxSign: (isMAS ? {
@@ -74,83 +75,116 @@ const config: ForgeConfig = {
7475
// Hooks for post-processing after packaging
7576
hooks: {
7677
postPackage: async (_config, options) => {
77-
// Only run for MAS builds on macOS
78-
if (!isMAS || options.platform !== 'mas') {
78+
const isMacBuild = options.platform === 'darwin' || options.platform === 'mas';
79+
if (!isMacBuild) {
7980
return;
8081
}
8182

82-
console.log('[postPackage] Re-signing helper apps with correct entitlements for MAS...');
83-
8483
const outputDir = options.outputPaths[0];
85-
const identity = process.env.SIGNING_IDENTITY_APPSTORE || 'Apple Distribution';
86-
const childEntitlements = path.resolve('entitlements.child.plist');
87-
const mainEntitlements = path.resolve('entitlements.mas.plist');
88-
const teamId = process.env.APPLE_TEAM_ID || '';
89-
const bundleId = (_config as any).packagerConfig.appBundleId || 'com.grabtaxi.klever';
90-
91-
// Find the .app bundle inside the output directory
9284
const items = fs.readdirSync(outputDir);
9385
const appBundle = items.find(item => item.endsWith('.app'));
94-
9586
if (!appBundle) {
96-
console.log('[postPackage] No .app bundle found in output directory, skipping');
97-
return;
87+
throw new Error('[postPackage] No .app bundle found in output directory');
9888
}
9989

10090
const appPath = path.join(outputDir, appBundle);
10191
const frameworksPath = path.join(appPath, 'Contents', 'Frameworks');
92+
const runtimeRoot = path.join(appPath, 'Contents', 'Resources', 'llama', 'bin');
10293

103-
console.log(`[postPackage] App bundle: ${appPath}`);
104-
console.log(`[postPackage] Frameworks path: ${frameworksPath}`);
94+
const runtimeArtifacts = fs.existsSync(runtimeRoot)
95+
? fs.readdirSync(runtimeRoot)
96+
.flatMap((platformDir) => {
97+
const platformDirPath = path.join(runtimeRoot, platformDir);
98+
if (!fs.existsSync(platformDirPath) || !fs.statSync(platformDirPath).isDirectory()) {
99+
return [];
100+
}
105101

106-
if (!fs.existsSync(frameworksPath)) {
107-
console.log('[postPackage] No Frameworks directory found, skipping helper re-signing');
108-
return;
102+
return fs.readdirSync(platformDirPath)
103+
.map((entry) => path.join(platformDirPath, entry))
104+
.filter((artifactPath) => {
105+
if (!fs.existsSync(artifactPath)) {
106+
return false;
107+
}
108+
const stat = fs.lstatSync(artifactPath);
109+
if (!stat.isFile()) {
110+
return false;
111+
}
112+
113+
const lowerName = path.basename(artifactPath).toLowerCase();
114+
return lowerName === 'llama-server'
115+
|| lowerName === 'llama-server.exe'
116+
|| lowerName.endsWith('.dylib')
117+
|| lowerName.endsWith('.so');
118+
});
119+
})
120+
: [];
121+
122+
if (runtimeArtifacts.length === 0) {
123+
throw new Error('[postPackage] Bundled llama runtime artifacts were not found in app package');
109124
}
110125

111-
// Find all helper apps
112-
const frameworkItems = fs.readdirSync(frameworksPath);
113-
const helperApps = frameworkItems.filter(item => item.endsWith('.app'));
126+
const runtimeDynamicLibraries = runtimeArtifacts.filter((artifactPath) => {
127+
const lowerName = path.basename(artifactPath).toLowerCase();
128+
return lowerName.endsWith('.dylib') || lowerName.endsWith('.so');
129+
});
130+
if (runtimeDynamicLibraries.length === 0) {
131+
throw new Error('[postPackage] Bundled llama runtime dynamic libraries were not found in app package');
132+
}
114133

115-
console.log(`[postPackage] Found ${helperApps.length} helper apps to re-sign`);
134+
if (isMAS && options.platform === 'mas') {
135+
console.log('[postPackage] Re-signing helper apps and bundled runtime for MAS...');
136+
const identity = process.env.SIGNING_IDENTITY_APPSTORE || 'Apple Distribution';
137+
const childEntitlements = path.resolve('entitlements.child.plist');
138+
const mainEntitlements = path.resolve('entitlements.mas.plist');
139+
const teamId = process.env.APPLE_TEAM_ID || '';
140+
const bundleId = (_config as any).packagerConfig.appBundleId || 'com.grabtaxi.klever';
116141

117-
for (const helperApp of helperApps) {
118-
const helperPath = path.join(frameworksPath, helperApp);
119-
console.log(`[postPackage] Re-signing helper: ${helperApp}`);
142+
console.log(`[postPackage] App bundle: ${appPath}`);
143+
console.log(`[postPackage] Frameworks path: ${frameworksPath}`);
120144

121-
try {
122-
// Re-sign the helper app with child entitlements (inherit only)
145+
if (!fs.existsSync(frameworksPath)) {
146+
console.log('[postPackage] No Frameworks directory found, skipping helper re-signing');
147+
} else {
148+
// Find all helper apps
149+
const frameworkItems = fs.readdirSync(frameworksPath);
150+
const helperApps = frameworkItems.filter(item => item.endsWith('.app'));
151+
152+
console.log(`[postPackage] Found ${helperApps.length} helper apps to re-sign`);
153+
154+
for (const helperApp of helperApps) {
155+
const helperPath = path.join(frameworksPath, helperApp);
156+
console.log(`[postPackage] Re-signing helper: ${helperApp}`);
157+
158+
try {
159+
// Re-sign the helper app with child entitlements (inherit only)
160+
execSync(
161+
`codesign --force --sign "${identity}" --entitlements "${childEntitlements}" --timestamp=none "${helperPath}"`,
162+
{ stdio: 'inherit' }
163+
);
164+
console.log(`[postPackage] ✅ Successfully re-signed: ${helperApp}`);
165+
} catch (error) {
166+
console.error(`[postPackage] ❌ Failed to re-sign ${helperApp}:`, error);
167+
throw error;
168+
}
169+
}
170+
}
171+
172+
for (const runtimeBinaryPath of runtimeArtifacts) {
173+
console.log(`[postPackage] Re-signing bundled runtime artifact: ${runtimeBinaryPath}`);
123174
execSync(
124-
`codesign --force --sign "${identity}" --entitlements "${childEntitlements}" --timestamp=none "${helperPath}"`,
175+
`codesign --force --sign "${identity}" --entitlements "${childEntitlements}" --timestamp=none "${runtimeBinaryPath}"`,
125176
{ stdio: 'inherit' }
126177
);
127-
console.log(`[postPackage] ✅ Successfully re-signed: ${helperApp}`);
128-
129-
// Verify the entitlements were applied
130-
try {
131-
const verifyOutput = execSync(
132-
`codesign -d --entitlements - "${helperPath}" 2>&1`,
133-
{ encoding: 'utf8' }
134-
);
135-
console.log(`[postPackage] Verifying entitlements for ${helperApp}:`);
136-
console.log(verifyOutput);
137-
} catch (verifyError) {
138-
console.error(`[postPackage] ⚠️ Failed to verify entitlements for ${helperApp}:`, verifyError);
139-
}
140-
} catch (error) {
141-
console.error(`[postPackage] ❌ Failed to re-sign ${helperApp}:`, error);
142-
throw error;
143178
}
144-
}
145179

146-
// Re-sign the main app to update the seal after helper modifications
147-
console.log('[postPackage] Re-signing main app to update seal...');
148-
try {
149-
// Create enhanced entitlements with application identifier for TestFlight
150-
const mainEntitlementsContent = fs.readFileSync(mainEntitlements, 'utf8');
151-
const enhancedEntitlements = mainEntitlementsContent.replace(
152-
'</dict>',
153-
` <!-- Required for TestFlight distribution -->
180+
// Re-sign the main app to update the seal after helper/runtime modifications
181+
console.log('[postPackage] Re-signing main app to update seal...');
182+
try {
183+
// Create enhanced entitlements with application identifier for TestFlight
184+
const mainEntitlementsContent = fs.readFileSync(mainEntitlements, 'utf8');
185+
const enhancedEntitlements = mainEntitlementsContent.replace(
186+
'</dict>',
187+
` <!-- Required for TestFlight distribution -->
154188
<key>com.apple.application-identifier</key>
155189
<string>${teamId}.${bundleId}</string>
156190
<key>com.apple.developer.team-identifier</key>
@@ -160,26 +194,32 @@ const config: ForgeConfig = {
160194
<string>${teamId}.${bundleId}</string>
161195
</array>
162196
</dict>`
163-
);
197+
);
164198

165-
const tempEntitlements = path.join(outputDir, 'temp-entitlements.plist');
166-
fs.writeFileSync(tempEntitlements, enhancedEntitlements);
199+
const tempEntitlements = path.join(outputDir, 'temp-entitlements.plist');
200+
fs.writeFileSync(tempEntitlements, enhancedEntitlements);
167201

168-
execSync(
169-
`codesign --force --sign "${identity}" --entitlements "${tempEntitlements}" --timestamp=none "${appPath}"`,
170-
{ stdio: 'inherit' }
171-
);
202+
execSync(
203+
`codesign --force --sign "${identity}" --entitlements "${tempEntitlements}" --timestamp=none "${appPath}"`,
204+
{ stdio: 'inherit' }
205+
);
172206

173-
// Clean up temp file
174-
fs.unlinkSync(tempEntitlements);
207+
// Clean up temp file
208+
fs.unlinkSync(tempEntitlements);
209+
210+
console.log('[postPackage] ✅ Successfully re-signed main app');
211+
} catch (error) {
212+
console.error('[postPackage] ❌ Failed to re-sign main app:', error);
213+
throw error;
214+
}
215+
}
175216

176-
console.log('[postPackage] ✅ Successfully re-signed main app');
177-
} catch (error) {
178-
console.error('[postPackage] ❌ Failed to re-sign main app:', error);
179-
throw error;
217+
for (const runtimeBinaryPath of runtimeArtifacts) {
218+
console.log(`[postPackage] Verifying bundled runtime signature: ${runtimeBinaryPath}`);
219+
execSync(`codesign --verify --verbose=2 "${runtimeBinaryPath}"`, { stdio: 'inherit' });
180220
}
181221

182-
console.log('[postPackage] Helper re-signing complete');
222+
console.log('[postPackage] Bundled runtime verification complete');
183223
},
184224
},
185225
makers: [

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
"type": "module",
1313
"main": ".vite/build/main.cjs",
1414
"scripts": {
15+
"prepare:llama-runtime": "node scripts/prepare-llama-runtime.mjs",
16+
"prepare:llama-runtime:refresh": "node scripts/prepare-llama-runtime.mjs --refresh --strict",
17+
"prepackage": "npm run prepare:llama-runtime",
18+
"premake": "npm run prepare:llama-runtime",
19+
"prestart": "npm run prepare:llama-runtime",
1520
"start": "electron-forge start",
1621
"package": "electron-forge package",
1722
"make": "electron-forge make",
@@ -23,7 +28,7 @@
2328
"make:all": "electron-forge make --platform=darwin,win32",
2429
"publish": "electron-forge publish",
2530
"lint": "eslint --ext .ts,.tsx .",
26-
"test:assistant": "tsx --test src/main/assistant/ToolSafetyPolicy.test.ts src/main/assistant/OllamaGuideService.test.ts",
31+
"test:assistant": "tsx --test src/main/assistant/*.test.ts",
2732
"generate:tools": "tsx scripts/generate-tools.ts",
2833
"version": "echo '✅ Version updated to' $npm_package_version",
2934
"release": "npm version patch && git push origin main --follow-tags",

runtime/llama/bin/.gitkeep

Whitespace-only changes.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"source": {
3+
"owner": "ggml-org",
4+
"repo": "llama.cpp"
5+
},
6+
"release": {
7+
"tag": "b8855",
8+
"name": "b8855",
9+
"publishedAt": "2026-04-20T12:55:44Z",
10+
"url": "https://github.com/ggml-org/llama.cpp/releases/tag/b8855"
11+
},
12+
"platforms": {
13+
"darwin-arm64": {
14+
"assetName": "llama-b8855-bin-macos-arm64.tar.gz",
15+
"assetUrl": "https://github.com/ggml-org/llama.cpp/releases/download/b8855/llama-b8855-bin-macos-arm64.tar.gz",
16+
"assetSha256": "b01357ce84a434bbc45e9f838b5620a635de63ceef305829e725f8c70a032c37",
17+
"lastResolvedAt": "2026-04-20T15:20:36.251Z",
18+
"binaryRelativePath": "bin/darwin-arm64/llama-server",
19+
"binarySha256": "07cc25e06d71512b462057e25142cf41b43746882b4ef30297cf274f44161d26",
20+
"preparedAt": "2026-04-20T15:36:51.799Z",
21+
"runtimeDependencyCount": 27
22+
},
23+
"darwin-x64": {
24+
"assetName": "llama-b8855-bin-macos-x64.tar.gz",
25+
"assetUrl": "https://github.com/ggml-org/llama.cpp/releases/download/b8855/llama-b8855-bin-macos-x64.tar.gz",
26+
"assetSha256": "5f229b3a815280737f2e8b93b49d8ca9c6d0d87bddb280c69266ea1a06daa3e2",
27+
"lastResolvedAt": "2026-04-20T15:20:36.252Z"
28+
},
29+
"windows-x64": {
30+
"assetName": "llama-b8855-bin-win-cpu-x64.zip",
31+
"assetUrl": "https://github.com/ggml-org/llama.cpp/releases/download/b8855/llama-b8855-bin-win-cpu-x64.zip",
32+
"assetSha256": "58b4f3f56250b8b2025c65964464b887d0f241e6f95577b2fdb84b14d4979b20",
33+
"lastResolvedAt": "2026-04-20T15:20:36.252Z",
34+
"binaryRelativePath": "bin/windows-x64/llama-server.exe",
35+
"binarySha256": "ea2239a647e835b1c9640694520211d5807daae795aff6d2c398ac8ac99efe64",
36+
"preparedAt": "2026-04-20T15:20:43.283Z"
37+
},
38+
"windows-arm64": {
39+
"assetName": "llama-b8855-bin-win-cpu-arm64.zip",
40+
"assetUrl": "https://github.com/ggml-org/llama.cpp/releases/download/b8855/llama-b8855-bin-win-cpu-arm64.zip",
41+
"assetSha256": "64a7416cc56b317439f922a460c08091c38ac7c7cbd0a8b93974f9d1a45a9981",
42+
"lastResolvedAt": "2026-04-20T15:20:36.252Z"
43+
}
44+
},
45+
"updatedAt": "2026-04-20T15:36:51.802Z"
46+
}

0 commit comments

Comments
 (0)