aegis-desktop 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/lib/deep-link.js +83 -0
- package/lib/local/engine.js +398 -29
- package/lib/local/tools.js +257 -13
- package/lib/quick-launcher.js +81 -0
- package/lib/settings.js +76 -0
- package/lib/sync/sessions.js +27 -0
- package/lib/window-state.js +63 -0
- package/main.js +971 -32
- package/package.json +8 -4
- package/preload.js +121 -0
- package/renderer/app.js +533 -16
- package/renderer/index.html +49 -0
- package/renderer/markdown.js +165 -0
- package/renderer/quick.css +126 -0
- package/renderer/quick.html +39 -0
- package/renderer/quick.js +188 -0
- package/renderer/style.css +341 -0
- package/renderer/vendor/NOTICE.md +20 -0
- package/renderer/vendor/aegis-highlight.js +135 -0
- package/renderer/vendor/marked.umd.js +80 -0
- package/renderer/vendor/purify.min.js +3 -0
- package/vendor/aegis.js +34 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aegis-desktop",
|
|
3
3
|
"productName": "AEGIS Desktop",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Thin Electron host for AEGIS — a local chat UI over the shared client/aegis.js transport. Ships transport + UI only; engine logic stays server-side.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "AEGIS Code",
|
|
@@ -29,16 +29,20 @@
|
|
|
29
29
|
"icon": "node scripts/generate-icon.mjs",
|
|
30
30
|
"dist": "npm run predist && electron-builder",
|
|
31
31
|
"dist:dir": "npm run predist && electron-builder --dir",
|
|
32
|
-
"check": "node --check main.js && node --check preload.js && node --check renderer/app.js && node --check renderer/max-tokens.js && node --check scripts/predist.mjs && node --check scripts/generate-icon.mjs && node --check lib/local/context.js && node --check lib/local/providers.js && node --check lib/local/ollama.js && node --check lib/local/engine.js && node --check lib/local/tools.js && node --check lib/local/prompt.js && node --check lib/local/shell.js && node --check lib/local/agents.js && node --check lib/settings.js && node --check lib/sync/sessions.js && node --check lib/sync/memory-queue.js && node --check bin/aegis.js",
|
|
32
|
+
"check": "node --check main.js && node --check preload.js && node --check renderer/app.js && node --check renderer/quick.js && node --check renderer/max-tokens.js && node --check renderer/markdown.js && node --check renderer/vendor/aegis-highlight.js && node --check scripts/predist.mjs && node --check scripts/generate-icon.mjs && node --check lib/local/context.js && node --check lib/local/providers.js && node --check lib/local/ollama.js && node --check lib/local/engine.js && node --check lib/local/tools.js && node --check lib/local/prompt.js && node --check lib/local/shell.js && node --check lib/local/agents.js && node --check lib/settings.js && node --check lib/sync/sessions.js && node --check lib/sync/memory-queue.js && node --check lib/window-state.js && node --check lib/deep-link.js && node --check lib/quick-launcher.js && node --check bin/aegis.js",
|
|
33
33
|
"test:shell": "node ../test/desktop-shell.mjs",
|
|
34
34
|
"test:model": "node ../test/model-dispatch.mjs",
|
|
35
35
|
"test:max-tokens": "node ../test/max-tokens.test.mjs",
|
|
36
36
|
"test:foreign-memory": "node ../test/foreign-memory.test.mjs",
|
|
37
37
|
"test:tools": "node ../test/local-tools.test.mjs",
|
|
38
|
-
"test:engine": "node ../test/local-engine.test.mjs"
|
|
38
|
+
"test:engine": "node ../test/local-engine.test.mjs",
|
|
39
|
+
"test:highlight": "node ../test/aegis-highlight.test.mjs",
|
|
40
|
+
"test:markdown": "node ../test/markdown.test.mjs",
|
|
41
|
+
"test:deep-link": "node test/deep-link.test.mjs"
|
|
39
42
|
},
|
|
40
43
|
"dependencies": {
|
|
41
|
-
"electron": "^33.0.0"
|
|
44
|
+
"electron": "^33.0.0",
|
|
45
|
+
"electron-updater": "^6.8.9"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
44
48
|
"electron-builder": "^25.1.8"
|
package/preload.js
CHANGED
|
@@ -16,7 +16,15 @@ const { contextBridge, ipcRenderer } = require('electron');
|
|
|
16
16
|
const IPC_PREFIX = 'aegis:';
|
|
17
17
|
const MODEL_PREFIX = 'model:';
|
|
18
18
|
const SYNC_PREFIX = 'sync:';
|
|
19
|
+
const QUICK_PREFIX = 'quick:';
|
|
19
20
|
const CHAT_DELTA_CHANNEL = `${IPC_PREFIX}chatDelta`;
|
|
21
|
+
const UPDATE_STATUS_CHANNEL = `${IPC_PREFIX}updateStatus`;
|
|
22
|
+
const MENU_NEW_CHAT_CHANNEL = `${IPC_PREFIX}menuNewChat`;
|
|
23
|
+
const MENU_SEARCH_CHANNEL = `${IPC_PREFIX}menuSearch`;
|
|
24
|
+
const MENU_EXPORT_MARKDOWN_CHANNEL = `${IPC_PREFIX}menuExportMarkdown`;
|
|
25
|
+
const MENU_EXPORT_JSON_CHANNEL = `${IPC_PREFIX}menuExportJson`;
|
|
26
|
+
const DEEP_LINK_CHANNEL = `${IPC_PREFIX}deepLink`;
|
|
27
|
+
const QUICK_LAUNCHER_PUSH_CHANNEL = `${IPC_PREFIX}quickLauncherPush`;
|
|
20
28
|
|
|
21
29
|
function invoke(name, payload) {
|
|
22
30
|
return ipcRenderer.invoke(
|
|
@@ -39,6 +47,13 @@ function invokeSync(name, payload) {
|
|
|
39
47
|
);
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
function invokeQuick(name, payload) {
|
|
51
|
+
return ipcRenderer.invoke(
|
|
52
|
+
QUICK_PREFIX + name,
|
|
53
|
+
payload === undefined ? undefined : payload
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
/**
|
|
43
58
|
* Build the chatDelta listener for one streaming request.
|
|
44
59
|
*
|
|
@@ -107,6 +122,83 @@ const api = {
|
|
|
107
122
|
memorySaveBatch: (entries) => invoke('memorySaveBatch', { entries }),
|
|
108
123
|
memoryImport: (payload) => invoke('memoryImport', payload || {}),
|
|
109
124
|
importConversation: (payload) => invoke('importConversation', payload || {}),
|
|
125
|
+
// Rendered markdown's links (and the discovery lane's) open in the OS
|
|
126
|
+
// default browser, never the app's own BrowserWindow — see main.js
|
|
127
|
+
// isSafeExternalUrl for the http/https-only allowlist.
|
|
128
|
+
openExternal: (url) => invoke('openExternal', { url }),
|
|
129
|
+
|
|
130
|
+
// Tool-call approval toggle (Settings → "Confirm before running tools"):
|
|
131
|
+
// main.js createConfirmModeDispatch persists it in the settings store's
|
|
132
|
+
// reserved `__confirmMode` namespace, and the engine's gate
|
|
133
|
+
// (lib/local/engine.js gatedExecuteTool) reads it on every mutating tool
|
|
134
|
+
// call. Both resolve `{ enabled }`.
|
|
135
|
+
getConfirmMode: () => invoke('getConfirmMode'),
|
|
136
|
+
setConfirmMode: (enabled) => invoke('setConfirmMode', { enabled: Boolean(enabled) }),
|
|
137
|
+
|
|
138
|
+
// Auto-update (electron-updater over GitHub Releases — see main.js
|
|
139
|
+
// createUpdateManager). check/download resolve the same status shape the
|
|
140
|
+
// push channel delivers; both are no-ops that resolve `{ status:
|
|
141
|
+
// 'disabled' }` in an unpackaged dev build. quitAndInstallUpdate must only
|
|
142
|
+
// ever be called from an explicit user action (the banner's "Restart to
|
|
143
|
+
// install" button) — main.js never restarts on its own.
|
|
144
|
+
checkForUpdates: () => invoke('checkForUpdates'),
|
|
145
|
+
downloadUpdate: () => invoke('downloadUpdate'),
|
|
146
|
+
quitAndInstallUpdate: () => invoke('quitAndInstallUpdate'),
|
|
147
|
+
updateStatus: () => invoke('updateStatus'),
|
|
148
|
+
// Live push as the state machine advances (checking -> available ->
|
|
149
|
+
// downloading -> downloaded, or -> error at any point). Returns an
|
|
150
|
+
// unsubscribe function, same shape as the chat delta listeners above.
|
|
151
|
+
onUpdateStatus: (onStatus) => {
|
|
152
|
+
const listener = (_event, state) => onStatus(state);
|
|
153
|
+
ipcRenderer.on(UPDATE_STATUS_CHANNEL, listener);
|
|
154
|
+
return () => ipcRenderer.removeListener(UPDATE_STATUS_CHANNEL, listener);
|
|
155
|
+
},
|
|
156
|
+
// Native menu accelerators with no built-in Electron role (Cmd/Ctrl+N,
|
|
157
|
+
// Cmd/Ctrl+K — see main.js buildAppMenu): main.js just pings the channel,
|
|
158
|
+
// the renderer owns what "new chat" / "search" actually do.
|
|
159
|
+
onMenuNewChat: (onTrigger) => {
|
|
160
|
+
const listener = () => onTrigger();
|
|
161
|
+
ipcRenderer.on(MENU_NEW_CHAT_CHANNEL, listener);
|
|
162
|
+
return () => ipcRenderer.removeListener(MENU_NEW_CHAT_CHANNEL, listener);
|
|
163
|
+
},
|
|
164
|
+
onMenuSearch: (onTrigger) => {
|
|
165
|
+
const listener = () => onTrigger();
|
|
166
|
+
ipcRenderer.on(MENU_SEARCH_CHANNEL, listener);
|
|
167
|
+
return () => ipcRenderer.removeListener(MENU_SEARCH_CHANNEL, listener);
|
|
168
|
+
},
|
|
169
|
+
// File > Save as… / Export Session… (see main.js buildAppMenu): the menu
|
|
170
|
+
// has no renderer state of its own, so it just pings which format was
|
|
171
|
+
// asked for; the renderer's handler knows the open session id and calls
|
|
172
|
+
// exportSession() below — the same path the sidebar's Export button uses.
|
|
173
|
+
onMenuExportMarkdown: (onTrigger) => {
|
|
174
|
+
const listener = () => onTrigger();
|
|
175
|
+
ipcRenderer.on(MENU_EXPORT_MARKDOWN_CHANNEL, listener);
|
|
176
|
+
return () => ipcRenderer.removeListener(MENU_EXPORT_MARKDOWN_CHANNEL, listener);
|
|
177
|
+
},
|
|
178
|
+
onMenuExportJson: (onTrigger) => {
|
|
179
|
+
const listener = () => onTrigger();
|
|
180
|
+
ipcRenderer.on(MENU_EXPORT_JSON_CHANNEL, listener);
|
|
181
|
+
return () => ipcRenderer.removeListener(MENU_EXPORT_JSON_CHANNEL, listener);
|
|
182
|
+
},
|
|
183
|
+
// Writes the given session to a user-picked file (dialog.showSaveDialog
|
|
184
|
+
// runs in main — see main.js createExportDispatch); resolves
|
|
185
|
+
// { ok, filePath } or { ok: false, canceled | reason }.
|
|
186
|
+
exportSession: (sessionId, format) => invoke('exportSession', { sessionId, format }),
|
|
187
|
+
// aegis:// deep link (main.js sendDeepLinkToWindow): fires with
|
|
188
|
+
// { action: 'open', sessionId } or { action: 'new', prompt }.
|
|
189
|
+
onDeepLink: (onLink) => {
|
|
190
|
+
const listener = (_event, parsed) => onLink(parsed);
|
|
191
|
+
ipcRenderer.on(DEEP_LINK_CHANNEL, listener);
|
|
192
|
+
return () => ipcRenderer.removeListener(DEEP_LINK_CHANNEL, listener);
|
|
193
|
+
},
|
|
194
|
+
// Quick launcher "add to chat" (main.js pushQuickLauncherResult): fires
|
|
195
|
+
// with { prompt, response, model } once the user pushes a launcher answer
|
|
196
|
+
// into the main window — see renderer/app.js's handler for what it builds.
|
|
197
|
+
onQuickLauncherPush: (onPush) => {
|
|
198
|
+
const listener = (_event, payload) => onPush(payload);
|
|
199
|
+
ipcRenderer.on(QUICK_LAUNCHER_PUSH_CHANNEL, listener);
|
|
200
|
+
return () => ipcRenderer.removeListener(QUICK_LAUNCHER_PUSH_CHANNEL, listener);
|
|
201
|
+
},
|
|
110
202
|
};
|
|
111
203
|
|
|
112
204
|
// Model-class surface (plan P1 §5.3): backed by the `model:` channels in
|
|
@@ -148,6 +240,14 @@ const models = {
|
|
|
148
240
|
remove: (provider) => invokeModel('settings.remove', { provider }),
|
|
149
241
|
},
|
|
150
242
|
cancel: (sessionId) => invokeModel('cancel', { sessionId }),
|
|
243
|
+
// Tool-call approval gate: the renderer's approval card calls this to
|
|
244
|
+
// answer a pending exec/writeFile/editFile request (the approval itself
|
|
245
|
+
// arrives as a `{ approval }` chunk on the same chat() delta stream — see
|
|
246
|
+
// deltaListener above). clearApprovals wipes a conversation's "allow for
|
|
247
|
+
// this session" grants; newChat() calls it so a fresh thread starts clean.
|
|
248
|
+
respondApproval: (approvalId, decision) =>
|
|
249
|
+
invokeModel('respondApproval', { approvalId, decision }),
|
|
250
|
+
clearApprovals: (sessionId) => invokeModel('clearApprovals', { sessionId }),
|
|
151
251
|
};
|
|
152
252
|
|
|
153
253
|
// Session sync surface (plan P1 §5.3 / P3 §7): local persistence now, cloud
|
|
@@ -163,6 +263,27 @@ const sync = {
|
|
|
163
263
|
status: () => invokeSync('status'),
|
|
164
264
|
};
|
|
165
265
|
|
|
266
|
+
// Quick launcher surface: loaded by BOTH renderer/index.html (the settings
|
|
267
|
+
// card that configures the global shortcut) and renderer/quick.html (the
|
|
268
|
+
// launcher popup itself, which calls pushToMain when the user keeps an
|
|
269
|
+
// answer) — one bridge, two consumers, same whitelist-only shape as
|
|
270
|
+
// aegis/models/sync above.
|
|
271
|
+
const quickLauncher = {
|
|
272
|
+
// { enabled, shortcut, packaged, active, reason } — see main.js
|
|
273
|
+
// createQuickLauncherDispatch. `active` reflects whether the shortcut is
|
|
274
|
+
// actually registered right now; `reason` explains a failed registration.
|
|
275
|
+
status: () => invokeQuick('status'),
|
|
276
|
+
setConfig: (cfg) =>
|
|
277
|
+
invokeQuick('setConfig', {
|
|
278
|
+
enabled: cfg && cfg.enabled,
|
|
279
|
+
shortcut: cfg && cfg.shortcut,
|
|
280
|
+
}),
|
|
281
|
+
// Called by renderer/quick.js once an answer exists; resolves
|
|
282
|
+
// { ok, reason? }.
|
|
283
|
+
pushToMain: (payload) => invokeQuick('pushToMain', payload || {}),
|
|
284
|
+
};
|
|
285
|
+
|
|
166
286
|
contextBridge.exposeInMainWorld('aegis', Object.freeze(api));
|
|
167
287
|
contextBridge.exposeInMainWorld('models', Object.freeze(models));
|
|
168
288
|
contextBridge.exposeInMainWorld('sync', Object.freeze(sync));
|
|
289
|
+
contextBridge.exposeInMainWorld('quickLauncher', Object.freeze(quickLauncher));
|