amalgm 0.1.143 → 0.1.144

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.
Files changed (57) hide show
  1. package/package.json +1 -1
  2. package/runtime/lib/local/amalgmStore.js +0 -7
  3. package/runtime/scripts/amalgm-mcp/apps/rest.js +24 -1
  4. package/runtime/scripts/amalgm-mcp/apps/supervisor.js +16 -0
  5. package/runtime/scripts/amalgm-mcp/browser/cli.js +1 -5
  6. package/runtime/scripts/amalgm-mcp/browser/engine.js +18 -3
  7. package/runtime/scripts/amalgm-mcp/browser/paths.js +6 -1
  8. package/runtime/scripts/amalgm-mcp/browser/rest.js +12 -5
  9. package/runtime/scripts/amalgm-mcp/computer-use/index.js +1038 -0
  10. package/runtime/scripts/amalgm-mcp/lib/email-embeds.js +53 -23
  11. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-aa-57534e-ffffff.png +0 -0
  12. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-nb-f37626-ffffff.png +0 -0
  13. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-p-d24726-ffffff.png +0 -0
  14. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-atsign-ffffff.png +0 -0
  15. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-box-d6d3d1.png +0 -0
  16. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-calendarclock-d6d3d1.png +0 -0
  17. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-file-a8a29e.png +0 -0
  18. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filearchive-facc15.png +0 -0
  19. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-fileimage-2dd4bf.png +0 -0
  20. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-fileimage-c084fc.png +0 -0
  21. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filespreadsheet-4ade80.png +0 -0
  22. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-60a5fa.png +0 -0
  23. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-a8a29e.png +0 -0
  24. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-f87171.png +0 -0
  25. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-folder-d6d3d1.png +0 -0
  26. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-globe-d6d3d1.png +0 -0
  27. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-video-f472b6.png +0 -0
  28. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-zap-fbbf24.png +0 -0
  29. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/manifest.json +56 -0
  30. package/runtime/scripts/amalgm-mcp/lib/email-icons.js +34 -1
  31. package/runtime/scripts/amalgm-mcp/lib/email-md.js +95 -51
  32. package/runtime/scripts/amalgm-mcp/lib/file-icon-registry.json +146 -3
  33. package/runtime/scripts/amalgm-mcp/lib/generated/email-render.json +153 -0
  34. package/runtime/scripts/amalgm-mcp/notify/index.js +5 -2
  35. package/runtime/scripts/amalgm-mcp/server/core-tools.js +6 -0
  36. package/runtime/scripts/amalgm-mcp/server/routes/apps.js +4 -0
  37. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-push.test.js +60 -0
  38. package/runtime/scripts/amalgm-mcp/tests/bundle-entries.test.js +54 -0
  39. package/runtime/scripts/amalgm-mcp/tests/computer-use.test.js +151 -0
  40. package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +4 -0
  41. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/citations.md +9 -0
  42. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/entities.md +15 -0
  43. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/links.md +5 -0
  44. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/media.md +21 -0
  45. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/text.md +30 -0
  46. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/citations.html +5 -0
  47. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/entities.html +8 -0
  48. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/links.html +3 -0
  49. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/media.html +11 -0
  50. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/notification-shell.html +11 -0
  51. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/text.html +12 -0
  52. package/runtime/scripts/amalgm-mcp/tests/email-render.test.js +82 -0
  53. package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +2 -0
  54. package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +5 -0
  55. package/runtime/scripts/amalgm-mcp/toolbox/loadout-context.js +1 -0
  56. package/runtime/scripts/amalgm-mcp/toolbox/mcp-surface.js +1 -0
  57. package/runtime/scripts/amalgm-mcp/toolbox/store.js +18 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.143",
3
+ "version": "0.1.144",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -48,8 +48,6 @@ var DIRECTORIES = [
48
48
  // ~/.amalgm/
49
49
  "browser",
50
50
  // ~/.amalgm/browser/
51
- "browser/local-storage",
52
- // ~/.amalgm/browser/local-storage/
53
51
  "agents",
54
52
  // ~/.amalgm/agents/
55
53
  "instructions",
@@ -70,11 +68,6 @@ var DEFAULT_FILES = {
70
68
  version: 1,
71
69
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
72
70
  },
73
- "browser/cookies.json": {
74
- version: 1,
75
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
76
- cookies: []
77
- },
78
71
  "agents/discovery.json": {
79
72
  version: 1,
80
73
  lastScan: null,
@@ -2,7 +2,7 @@
2
2
  * /apps/* REST routes for the Next.js API and local tunnel runtime.
3
3
  */
4
4
 
5
- const { getConnectedRoutes, loadApps } = require('./store');
5
+ const { getApp, getConnectedRoutes, loadApps, updateAppMeta } = require('./store');
6
6
  const {
7
7
  deleteApp,
8
8
  connectDns,
@@ -109,6 +109,28 @@ async function handleDelete(body, sendJson, options = {}) {
109
109
  }
110
110
  }
111
111
 
112
+ async function handleUpdate(body, sendJson, options = {}) {
113
+ try {
114
+ const appId = appIdFromBody(body);
115
+ if (!appId) return sendJson(400, { error: 'app_id is required' });
116
+ if (!getApp(appId)) return sendJson(404, { error: 'App not found' });
117
+
118
+ const updates = {};
119
+ if (typeof body?.name === 'string' && body.name.trim()) updates.name = body.name.trim();
120
+ if (typeof body?.description === 'string') updates.description = body.description;
121
+ const toolIds = body?.tool_ids ?? body?.toolIds;
122
+ if (Array.isArray(toolIds)) updates.toolIds = toolIds;
123
+ if (Object.keys(updates).length === 0) {
124
+ return sendJson(400, { error: 'No updatable fields provided (name, description, tool_ids)' });
125
+ }
126
+
127
+ const app = updateAppMeta(appId, updates);
128
+ sendJson(200, appPayload(app, options));
129
+ } catch (err) {
130
+ sendJson(400, { error: err instanceof Error ? err.message : String(err) });
131
+ }
132
+ }
133
+
112
134
  async function handleConnectDns(body, sendJson, options = {}) {
113
135
  try {
114
136
  const appId = appIdFromBody(body);
@@ -141,4 +163,5 @@ module.exports = {
141
163
  handleRoutes,
142
164
  handleStart,
143
165
  handleStop,
166
+ handleUpdate,
144
167
  };
@@ -390,10 +390,26 @@ async function deleteApp(appId) {
390
390
  stopping.delete(appId);
391
391
  running.delete(appId);
392
392
  await syncRoutesBestEffort(`delete:${appId}`);
393
+ clearAppLinkFromTools(appId);
393
394
 
394
395
  return app;
395
396
  }
396
397
 
398
+ // Keep the app↔tool relation from drifting: tools pointing at a deleted app
399
+ // must lose their appId, or sharing them would chase a dead dependency.
400
+ function clearAppLinkFromTools(appId) {
401
+ try {
402
+ const { readToolbox, updateTool } = require('../toolbox/store');
403
+ const owned = Object.values(readToolbox().tools)
404
+ .filter((tool) => tool.appId === appId && tool.origin !== 'system');
405
+ for (const tool of owned) {
406
+ updateTool({ id: tool.id, appId: null });
407
+ }
408
+ } catch (error) {
409
+ console.warn('[Apps] Failed to clear app link from tools:', error.message);
410
+ }
411
+ }
412
+
397
413
  async function registerApp(input) {
398
414
  const name = String(input.name || '').trim() || 'App';
399
415
  const cwd = path.resolve(input.cwd || input.root_dir || DEFAULT_CWD);
@@ -21,6 +21,7 @@ const os = require('os');
21
21
  const path = require('path');
22
22
  const { spawn } = require('child_process');
23
23
  const { amalgmDir: resolveAmalgmDir } = require('../../lib/runtime-paths');
24
+ const { safeId } = require('./paths');
24
25
 
25
26
  const DEFAULT_TIMEOUT_MS = 120_000;
26
27
  const DEFAULT_SESSION = 'default';
@@ -35,11 +36,6 @@ function ensureDir(dir) {
35
36
  return dir;
36
37
  }
37
38
 
38
- function safeId(value, fallback = DEFAULT_SESSION) {
39
- const raw = String(value || fallback).replace(/^agent-browser:/, '');
40
- return raw.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 64) || fallback;
41
- }
42
-
43
39
  function browserRoot() {
44
40
  return ensureDir(path.join(amalgmDir(), 'browser'));
45
41
  }
@@ -79,13 +79,28 @@ async function loadStateIntoSession(session, statePayload, context) {
79
79
 
80
80
  async function ensureCookieSourceLoaded(session, context) {
81
81
  if (cookieSourceDisabled()) return;
82
+ // Attached sessions drive the desktop app's own jar — the jar the cookie
83
+ // source is exported FROM (electron/browserCookieSource.ts). Importing the
84
+ // bundle back into it is at best an echo and at worst overwrites the user's
85
+ // live cookies with older ones. Only headless sessions import.
86
+ if (sessions.backendFor(session) === 'attached') return;
82
87
  const bundle = getBrowserAuthBundle(COOKIE_SOURCE_BUNDLE_ID);
83
88
  if (!bundle?.blobPath) return;
84
89
  const version = cookieSourceVersion(bundle);
85
90
  if (version && loadedCookieSources.get(session) === version) return;
86
- const { payload } = readEncryptedStateBundle(COOKIE_SOURCE_BUNDLE_ID);
87
- if (!payload?.state || typeof payload.state !== 'object') return;
88
- await loadStateIntoSession(session, payload.state, context);
91
+ try {
92
+ const { payload } = readEncryptedStateBundle(COOKIE_SOURCE_BUNDLE_ID);
93
+ if (payload?.state && typeof payload.state === 'object') {
94
+ await loadStateIntoSession(session, payload.state, context);
95
+ console.error(`[browser] cookie source ${String(version).slice(0, 12)} imported into session ${session}`);
96
+ }
97
+ } catch (err) {
98
+ // A bad bundle (key mismatch, corrupt blob, load failure) must degrade to
99
+ // a logged-out session, never break every browser verb.
100
+ console.error(`[browser] cookie source import failed for session ${session}: ${err.message}`);
101
+ }
102
+ // Recorded even on failure: the failure is a property of this bundle
103
+ // version, so retrying on every verb would only repeat the error.
89
104
  if (version) loadedCookieSources.set(session, version);
90
105
  }
91
106
 
@@ -10,9 +10,14 @@ function ensurePrivateDir(dir) {
10
10
  return dir;
11
11
  }
12
12
 
13
+ // The one id sanitizer for the browser module — cli.js re-exports it. The
14
+ // 64-char cap matches the profile directories cli.js actually creates;
15
+ // history note: cli.js and this file once disagreed (64 vs 96), which would
16
+ // have put the chromium profile and its registry record at different paths
17
+ // for long ids.
13
18
  function safeId(value, fallback = 'default') {
14
19
  const raw = String(value || fallback).replace(/^agent-browser:/, '');
15
- return raw.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 96) || fallback;
20
+ return raw.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 64) || fallback;
16
21
  }
17
22
 
18
23
  function browserRoot() {
@@ -53,11 +53,18 @@ function handleAuthBundlesList(sendJson) {
53
53
 
54
54
  async function exportAuthBundleFromProfile(body = {}) {
55
55
  const profileId = profileIdFrom(body);
56
- const saved = await saveStateBrowser({
57
- ...body,
58
- browserProfileId: profileId,
59
- browserSessionId: profileId,
60
- }, {});
56
+ // A caller that already holds the jar (the desktop app pushing its Electron
57
+ // session, see electron/browserCookieSource.ts) sends `state` directly.
58
+ // Capturing via a browser session here would read whichever backend the
59
+ // in-process session registry happens to name — after a runtime restart
60
+ // that is an empty headless profile, not the jar the user logged into.
61
+ const saved = body.state && typeof body.state === 'object'
62
+ ? { state: body.state, backend: 'state-push' }
63
+ : await saveStateBrowser({
64
+ ...body,
65
+ browserProfileId: profileId,
66
+ browserSessionId: profileId,
67
+ }, {});
61
68
 
62
69
  const bundle = writeEncryptedStateBundle({
63
70
  id: body.id || (profileId === DEFAULT_COOKIE_SOURCE_PROFILE_ID ? COOKIE_SOURCE_BUNDLE_ID : undefined),