amalgm 0.1.51 → 0.1.52

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 (70) hide show
  1. package/lib/tunnel-events.js +48 -23
  2. package/package.json +2 -2
  3. package/runtime/lib/harnesses.js +12 -4
  4. package/runtime/scripts/amalgm-mcp/agents/store.js +5 -5
  5. package/runtime/scripts/amalgm-mcp/{artifacts → apps}/advertise.js +39 -24
  6. package/runtime/scripts/amalgm-mcp/apps/rest.js +144 -0
  7. package/runtime/scripts/amalgm-mcp/apps/store.js +171 -0
  8. package/runtime/scripts/amalgm-mcp/apps/supervisor.js +439 -0
  9. package/runtime/scripts/amalgm-mcp/apps/tools.js +176 -0
  10. package/runtime/scripts/amalgm-mcp/automations/cell-references.js +237 -0
  11. package/runtime/scripts/amalgm-mcp/automations/context.js +41 -0
  12. package/runtime/scripts/amalgm-mcp/automations/rest.js +148 -0
  13. package/runtime/scripts/amalgm-mcp/automations/runner.js +613 -0
  14. package/runtime/scripts/amalgm-mcp/automations/scheduler.js +90 -0
  15. package/runtime/scripts/amalgm-mcp/automations/store.js +1125 -0
  16. package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +177 -0
  17. package/runtime/scripts/amalgm-mcp/automations/tools.js +418 -0
  18. package/runtime/scripts/amalgm-mcp/automations/validator.js +225 -0
  19. package/runtime/scripts/amalgm-mcp/browser/agent-browser.js +505 -0
  20. package/runtime/scripts/amalgm-mcp/browser/electron-bridge.js +222 -0
  21. package/runtime/scripts/amalgm-mcp/browser/page.js +13 -631
  22. package/runtime/scripts/amalgm-mcp/browser/tools.js +9 -7
  23. package/runtime/scripts/amalgm-mcp/config.js +33 -48
  24. package/runtime/scripts/amalgm-mcp/deps.js +1 -31
  25. package/runtime/scripts/amalgm-mcp/events/ingress.js +50 -42
  26. package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +169 -0
  27. package/runtime/scripts/amalgm-mcp/events/matcher.js +45 -14
  28. package/runtime/scripts/amalgm-mcp/events/store.js +106 -57
  29. package/runtime/scripts/amalgm-mcp/index.js +12 -14
  30. package/runtime/scripts/amalgm-mcp/lib/prefs.js +229 -65
  31. package/runtime/scripts/amalgm-mcp/lib/tool-result.js +13 -27
  32. package/runtime/scripts/amalgm-mcp/server/core-tools.js +2 -3
  33. package/runtime/scripts/amalgm-mcp/server/http.js +106 -56
  34. package/runtime/scripts/amalgm-mcp/slack/inbound.js +1 -1
  35. package/runtime/scripts/amalgm-mcp/state/db.js +119 -0
  36. package/runtime/scripts/amalgm-mcp/state/snapshot.js +16 -3
  37. package/runtime/scripts/amalgm-mcp/tasks/executor.js +1 -1
  38. package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +348 -0
  39. package/runtime/scripts/amalgm-mcp/tests/events-matcher.test.js +23 -0
  40. package/runtime/scripts/amalgm-mcp/tests/workflows-store-runner.test.js +67 -0
  41. package/runtime/scripts/amalgm-mcp/toolbox/tools.js +16 -3
  42. package/runtime/scripts/amalgm-mcp/workflows/compiler.js +222 -0
  43. package/runtime/scripts/amalgm-mcp/workflows/runner.js +593 -0
  44. package/runtime/scripts/amalgm-mcp/workflows/store.js +237 -0
  45. package/runtime/scripts/chat-core/adapters/claude.js +2 -1
  46. package/runtime/scripts/chat-core/auth.js +82 -12
  47. package/runtime/scripts/chat-core/contract.js +5 -1
  48. package/runtime/scripts/chat-core/engine.js +103 -62
  49. package/runtime/scripts/chat-core/event-schema.js +8 -0
  50. package/runtime/scripts/chat-core/events.js +5 -0
  51. package/runtime/scripts/chat-core/normalizers/codex.js +13 -1
  52. package/runtime/scripts/chat-core/parts.js +21 -6
  53. package/runtime/scripts/chat-core/sse.js +3 -0
  54. package/runtime/scripts/chat-core/tests/auth.test.js +84 -6
  55. package/runtime/scripts/chat-core/tests/engine.test.js +312 -0
  56. package/runtime/scripts/chat-core/tests/native-config.test.js +23 -0
  57. package/runtime/scripts/chat-core/tool-shape.js +4 -4
  58. package/runtime/scripts/chat-core/tooling/active-memory.js +5 -4
  59. package/runtime/scripts/chat-core/tooling/native-config.js +34 -3
  60. package/runtime/scripts/local-gateway.js +34 -27
  61. package/runtime/scripts/platform-context.txt +76 -94
  62. package/runtime/scripts/amalgm-mcp/artifacts/rest.js +0 -103
  63. package/runtime/scripts/amalgm-mcp/artifacts/store.js +0 -157
  64. package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +0 -439
  65. package/runtime/scripts/amalgm-mcp/artifacts/tools.js +0 -176
  66. package/runtime/scripts/amalgm-mcp/events/executor.js +0 -258
  67. package/runtime/scripts/amalgm-mcp/events/rest.js +0 -214
  68. package/runtime/scripts/amalgm-mcp/events/tools.js +0 -323
  69. package/runtime/scripts/amalgm-mcp/tasks/rest.js +0 -110
  70. package/runtime/scripts/amalgm-mcp/tasks/tools.js +0 -416
@@ -9,6 +9,7 @@ const EXCLUDED_DIR_NAMES = new Set([
9
9
  '.npm',
10
10
  '.tmp',
11
11
  'cache',
12
+ 'debug',
12
13
  'generated_images',
13
14
  'log',
14
15
  'logs',
@@ -44,6 +45,11 @@ function shouldCopyConfigPath(root, source) {
44
45
  if (!relative || relative === '.') return true;
45
46
  const parts = relative.split(path.sep);
46
47
  if (parts.some((part) => EXCLUDED_DIR_NAMES.has(part))) return false;
48
+ try {
49
+ if (fs.lstatSync(source).isSymbolicLink()) return false;
50
+ } catch {
51
+ return false;
52
+ }
47
53
  const basename = path.basename(source);
48
54
  return !EXCLUDED_FILE_PATTERNS.some((pattern) => pattern.test(basename));
49
55
  }
@@ -92,6 +98,28 @@ function ensureHomeAlias(runtimeHome, dotDirName) {
92
98
  }
93
99
  }
94
100
 
101
+ function removeOutboundSymlink(root, relativePath) {
102
+ const file = path.join(root, relativePath);
103
+ let stat;
104
+ try {
105
+ stat = fs.lstatSync(file);
106
+ } catch {
107
+ return;
108
+ }
109
+ if (!stat.isSymbolicLink()) return;
110
+ let target;
111
+ try {
112
+ target = fs.readlinkSync(file);
113
+ } catch {
114
+ return;
115
+ }
116
+ const resolvedRoot = path.resolve(root);
117
+ const resolvedTarget = path.resolve(path.dirname(file), target);
118
+ if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) {
119
+ fs.rmSync(file, { force: true });
120
+ }
121
+ }
122
+
95
123
  function nativeHome() {
96
124
  return process.env.AMALGM_NATIVE_HOME || os.homedir();
97
125
  }
@@ -170,12 +198,15 @@ function syncCodexNativeConfig(runtimeHome) {
170
198
 
171
199
  function syncClaudeNativeConfig(runtimeHome) {
172
200
  if (!runtimeHome) return null;
173
- const sourceDir = path.join(nativeHome(), '.claude');
201
+ const home = nativeHome();
202
+ if (path.resolve(runtimeHome) === path.resolve(home)) return null;
203
+ const sourceDir = path.join(home, '.claude');
174
204
  fs.mkdirSync(runtimeHome, { recursive: true });
205
+ removeOutboundSymlink(runtimeHome, path.join('debug', 'latest'));
175
206
  const copied = copyConfigTree(sourceDir, runtimeHome);
176
207
  ensureHomeAlias(runtimeHome, '.claude');
177
- copyFileIfPresent(path.join(nativeHome(), '.claude.json'), path.join(runtimeHome, '.claude.json'));
178
- copyConfigTree(path.join(nativeHome(), '.config', 'claude'), path.join(runtimeHome, '.config', 'claude'));
208
+ copyFileIfPresent(path.join(home, '.claude.json'), path.join(runtimeHome, '.claude.json'));
209
+ copyConfigTree(path.join(home, '.config', 'claude'), path.join(runtimeHome, '.config', 'claude'));
179
210
  return copied ? { sourceDir, runtimeHome } : null;
180
211
  }
181
212
 
@@ -46,10 +46,12 @@ const pty = loadPty();
46
46
 
47
47
  const AMALGM_DIR = process.env.AMALGM_DIR || path.join(os.homedir(), '.amalgm');
48
48
  const STATE_FILE = path.join(AMALGM_DIR, 'runtime-state.json');
49
- const ARTIFACTS_FILE = path.join(AMALGM_DIR, 'artifacts.json');
49
+ const APPS_FILE = path.join(AMALGM_DIR, 'apps.json');
50
+ const LEGACY_ARTIFACTS_FILE = path.join(AMALGM_DIR, 'artifacts.json');
50
51
  const LOG_DIR = path.join(AMALGM_DIR, 'logs');
51
52
  const BIND_HOST = process.env.AMALGM_BIND_HOST || '127.0.0.1';
52
53
  const OWNER = process.env.AMALGM_RUNTIME_SOURCE || 'local';
54
+ const FORCE_RUNTIME_STATE_OWNER = process.env.AMALGM_RUNTIME_FORCE_STATE_OWNER === 'true';
53
55
  const VERSION = process.env.npm_package_version || process.env.AMALGM_RUNTIME_VERSION || '';
54
56
  const DEFAULT_CWD = process.env.AMALGM_DEFAULT_CWD || os.homedir();
55
57
  const PORT = runtimePort('local-gateway');
@@ -122,6 +124,7 @@ const MCP_PREFIXES = [
122
124
  '/mcp',
123
125
  '/events',
124
126
  '/tasks',
127
+ '/automations',
125
128
  '/event-triggers',
126
129
  '/workspace',
127
130
  '/github',
@@ -134,6 +137,7 @@ const MCP_PREFIXES = [
134
137
  '/email',
135
138
  '/slack',
136
139
  '/user-api-keys',
140
+ '/apps',
137
141
  '/artifacts',
138
142
  '/agents',
139
143
  ];
@@ -146,22 +150,24 @@ const PORT_MONITOR_PREFIXES = [
146
150
  const ptySessions = new Map();
147
151
  const monitoredPreviewPorts = new Set();
148
152
 
149
- function readArtifacts() {
150
- const data = readJson(ARTIFACTS_FILE, { artifacts: [] });
151
- return Array.isArray(data?.artifacts) ? data.artifacts : [];
153
+ function readApps() {
154
+ const data = readJson(APPS_FILE, null);
155
+ if (Array.isArray(data?.apps)) return data.apps;
156
+ const legacy = readJson(LEGACY_ARTIFACTS_FILE, { artifacts: [] });
157
+ return Array.isArray(legacy?.artifacts) ? legacy.artifacts : [];
152
158
  }
153
159
 
154
- function normalizeArtifactRef(artifact) {
155
- return String(artifact?.artifactRef || artifact?.artifact_ref || '').trim();
160
+ function normalizeAppRef(app) {
161
+ return String(app?.appRef || app?.app_ref || app?.artifactRef || app?.artifact_ref || '').trim();
156
162
  }
157
163
 
158
- function isArtifactRoutable(artifact) {
159
- const port = Number(artifact?.port);
160
- const connected = artifact?.dnsConnected !== false && artifact?.connected !== false;
161
- const desiredRunning = (artifact?.desiredState || 'running') === 'running';
162
- const active = artifact?.status !== 'stopped' && artifact?.status !== 'error';
164
+ function isAppRoutable(app) {
165
+ const port = Number(app?.port);
166
+ const connected = app?.dnsConnected !== false && app?.connected !== false;
167
+ const desiredRunning = (app?.desiredState || 'running') === 'running';
168
+ const active = app?.status !== 'stopped' && app?.status !== 'error';
163
169
  return (
164
- /^[a-z0-9]{8,24}$/.test(normalizeArtifactRef(artifact))
170
+ /^[a-z0-9]{8,24}$/.test(normalizeAppRef(app))
165
171
  && Number.isInteger(port)
166
172
  && port > 0
167
173
  && port <= 65535
@@ -171,14 +177,14 @@ function isArtifactRoutable(artifact) {
171
177
  );
172
178
  }
173
179
 
174
- function findArtifactByRef(artifactRef) {
175
- return readArtifacts().find((artifact) =>
176
- normalizeArtifactRef(artifact) === artifactRef && isArtifactRoutable(artifact),
180
+ function findAppByRef(appRef) {
181
+ return readApps().find((app) =>
182
+ normalizeAppRef(app) === appRef && isAppRoutable(app),
177
183
  ) || null;
178
184
  }
179
185
 
180
- function activeArtifactPorts() {
181
- return new Set(readArtifacts().filter(isArtifactRoutable).map((artifact) => Number(artifact.port)));
186
+ function activeAppPorts() {
187
+ return new Set(readApps().filter(isAppRoutable).map((app) => Number(app.port)));
182
188
  }
183
189
 
184
190
  function isInternalRuntimePort(port) {
@@ -194,7 +200,7 @@ function isAllowedPreviewPort(port) {
194
200
  && port > 0
195
201
  && port <= 65535
196
202
  && !isInternalRuntimePort(port)
197
- && (activeArtifactPorts().has(port) || monitoredPreviewPorts.has(port))
203
+ && (activeAppPorts().has(port) || monitoredPreviewPorts.has(port))
198
204
  );
199
205
  }
200
206
 
@@ -285,6 +291,7 @@ function isProcessRunning(pid) {
285
291
  }
286
292
 
287
293
  function runtimeStateOwnedHere(previous) {
294
+ if (FORCE_RUNTIME_STATE_OWNER) return true;
288
295
  if (!previous || typeof previous !== 'object' || Object.keys(previous).length === 0) return true;
289
296
  const previousPids = [previous.pid, previous.gateway_pid, previous.supervisor_pid]
290
297
  .filter((pid) => Number.isInteger(pid) && pid > 0);
@@ -590,17 +597,17 @@ function forwardPathAfterPrefix(req, prefix) {
590
597
  }
591
598
 
592
599
  function externalProxyTargetForPath(pathname) {
593
- const artifactMatch = pathname.match(/^\/__amalgm\/artifacts\/([a-z0-9]{8,24})(?:\/|$)/);
594
- if (artifactMatch) {
595
- const artifactRef = artifactMatch[1];
596
- const artifact = findArtifactByRef(artifactRef);
597
- if (!artifact) {
598
- return { error: { status: 404, message: `Artifact is not routable: ${artifactRef}` } };
600
+ const appMatch = pathname.match(/^\/__amalgm\/(?:apps|artifacts)\/([a-z0-9]{8,24})(?:\/|$)/);
601
+ if (appMatch) {
602
+ const appRef = appMatch[1];
603
+ const app = findAppByRef(appRef);
604
+ if (!app) {
605
+ return { error: { status: 404, message: `App is not routable: ${appRef}` } };
599
606
  }
600
607
  return {
601
- port: Number(artifact.port),
602
- prefix: `/__amalgm/artifacts/${artifactRef}`,
603
- kind: 'artifact',
608
+ port: Number(app.port),
609
+ prefix: appMatch[0].endsWith('/') ? appMatch[0].slice(0, -1) : appMatch[0],
610
+ kind: 'app',
604
611
  };
605
612
  }
606
613
 
@@ -7,122 +7,105 @@ You are running inside Amalgm — an always-on, event-driven, multi-agent AI pla
7
7
 
8
8
  Amalgm has five core primitives that work together as a connected system:
9
9
 
10
- 1. **Tasks** — Scheduled and recurring work that runs autonomously on cron, interval, or one-shot schedules.
11
- 2. **Event Triggers** — Webhook endpoints that fire agent runs when external events arrive (GitHub pushes, Stripe payments, form submissions, etc.).
12
- 3. **Artifacts** — Registered services that can run on a user's connected computer and be exposed on `*.artifacts.amalgm.ai`. They are local-first, bootable, and persistent across Amalgm restarts.
13
- 4. **Notifications** — Email the user about results, completions, errors, or anything important. Soon: more channels.
14
- 5. **Agents** — Discover and talk to other agents on the platform for multi-turn collaboration and task delegation.
10
+ 1. **Automations** — One or more triggers plus one local workflow. Triggers can be scheduled or webhook/event driven; workflows run explicit cells and do not wake agents by default.
11
+ 2. **Apps** — Registered services that can run on a user's connected computer and be exposed on `*.apps.amalgm.ai`. They are local-first, bootable, and persistent across Amalgm restarts.
12
+ 3. **Notifications** — Email the user about results, completions, errors, or anything important. Soon: more channels.
13
+ 4. **Agents** — Discover and talk to other agents on the platform for multi-turn collaboration and task delegation.
15
14
 
16
- These primitives compose. A webhook arrives → triggers an agent runthe agent processes the payload → builds/updates an artifactnotifies the user via email. Or: a scheduled task polls an API → detects a change → emits an event another agent handles it. **Think in terms of these building blocks.**
15
+ These primitives compose. A webhook arrives → triggers an automation workflowworkflow cells process the payload → build/update an appnotify the user via email. Or: a scheduled trigger polls an API → detects a change → emits a result or explicitly calls an agent/tool. **Think in terms of these building blocks.**
17
16
 
18
17
  ## Be Proactive
19
18
 
20
19
  You should actively suggest automations when they would help. Examples:
21
- - User asks "let me know when my PR is merged" → **suggest creating an event trigger** with a GitHub webhook.
22
- - User asks "check this endpoint every hour" → **create a scheduled task**.
23
- - User asks "I need a dashboard for X" → **build an artifact**.
24
- - User asks "monitor my inbox" → Gmail doesn't support webhooks, so **build an artifact that uses IMAP polling + event emission** to bridge the gap.
25
- - After any background/scheduled/event-triggered run → **notify the user** via `notify_user` so they know what happened.
20
+ - User asks "let me know when my PR is merged" → **suggest creating an automation** with a GitHub webhook trigger.
21
+ - User asks "check this endpoint every hour" → **create an automation** with a scheduled trigger.
22
+ - User asks "I need a dashboard for X" → **build an app**.
23
+ - User asks "monitor my inbox" → Gmail doesn't support webhooks, so **build an app that uses IMAP polling + event emission** to bridge the gap.
24
+ - After any background or automation-triggered run → **notify the user** via `notify_user` when the user should know what happened.
26
25
 
27
- **Bridge-the-gap pattern:** When a service doesn't support webhooks natively, build an artifact (or MCP server) that polls the service on a schedule and emits events into the Amalgm event system. This turns any API into a reactive event source. For example: an IMAP email poller artifact, an RSS feed watcher, a status page monitor.
26
+ **Bridge-the-gap pattern:** When a service doesn't support webhooks natively, build an app (or MCP server) that polls the service on a schedule and emits events into the Amalgm event system. This turns any API into a reactive event source. For example: an IMAP email poller app, an RSS feed watcher, a status page monitor.
28
27
 
29
- **Always close the loop.** If a task runs in the background, runs on a schedule, or is triggered by an event call `notify_user` at the end so the user knows what happened. Don't assume they're watching.
28
+ **Always close the loop.** If an automation runs in the background, runs on a schedule, or is triggered by an event, call `notify_user` at the end when the user needs the result. Don't assume they're watching.
30
29
 
31
30
  ---
32
31
 
33
- ## Primitive 1: TasksScheduled & Recurring Work
32
+ ## Primitive 1: AutomationsTriggers + Workflow
34
33
 
35
- Create, manage, and trigger scheduled tasks that execute prompts autonomously.
34
+ Create, manage, and run local automations. An automation is one or more triggers plus one workflow. The workflow is source text compiled into strict workflow IR and stored locally in SQLite on the user's computer.
36
35
 
37
- - `scheduled_tasks_create` — Create a task with a cron, once, or interval schedule.
38
- - `scheduled_tasks_list` — List all tasks and their current status.
39
- - `scheduled_tasks_get` — Get details on a specific task, including recent run logs.
40
- - `scheduled_tasks_update` — Modify a task's schedule, prompt, enabled state, or concurrency limits.
41
- - `scheduled_tasks_delete` — Permanently remove a task.
42
- - `scheduled_tasks_run_now` — Trigger a task immediately, outside its schedule.
43
- - `scheduled_tasks_cancel` — Cancel a currently running task execution.
44
- - `scheduled_tasks_history` — Retrieve execution history for a task.
36
+ - `automations_create` — Create an automation with scheduled and/or event triggers plus a workflow.
37
+ - `automations_list` — List automations with trigger details, webhook URLs, secrets, workflow summaries, and status.
38
+ - `automations_get` — Get one automation including workflow source/IR and recent runs.
39
+ - `automations_update` — Modify automation metadata, triggers, enabled state, workflow source, allowlist, or limits.
40
+ - `automations_delete` — Permanently remove an automation.
41
+ - `automations_run_now` — Trigger an automation workflow immediately outside its configured triggers.
45
42
 
46
- Tasks can run indefinitely (cron), a fixed number of times, or once. They can have end dates (`endsAt`). They support concurrency limits. They can target specific agents and models.
47
-
48
- **When to use tasks:** Monitoring, polling APIs, periodic builds, recurring reports, health checks, cleanup jobs — anything that should happen on a schedule without user intervention.
49
-
50
- ## Primitive 2: Event Triggers — Webhook-Driven Agent Runs
51
-
52
- Create webhook endpoints that fire agent runs when external services send events.
53
-
54
- - `event_triggers_create` — Create a trigger. Returns a **webhook URL** and **secret** the user configures in the external service.
55
- - `event_triggers_list` — List all triggers with their webhook URLs and secrets.
56
- - `event_triggers_get` — Get details about a specific trigger.
57
- - `event_triggers_update` — Modify a trigger's prompt, source, event labels, or enabled state.
58
- - `event_triggers_delete` — Delete a trigger (webhook URL stops working).
59
-
60
- When a signed event arrives, the platform verifies the request secret first, then injects the payload into the `agent_prompt` template (via `{payload}` placeholder), and starts an agent run.
43
+ Scheduled triggers support cron, one-shot, and interval schedules. Event triggers expose webhook URLs and secrets. Workflows can use `code(...)`, gated local `cli(...)`, gated `http(...)`, and Toolbox/core `tool(...)` cells. They do not wake agents by default; add an explicit tool cell if an agent should be involved.
61
44
 
62
45
  **Supported auth formats:** GitHub-style HMAC headers (`x-hub-signature-256`), generic HMAC headers (`x-webhook-signature`), token headers (`Authorization: Bearer <secret>`, `x-amalgm-webhook-secret`, `x-webhook-secret`, `x-gitlab-token`), and first-party senders that sign the raw JSON body with the trigger secret. Unsigned requests are rejected.
63
46
 
64
- **When to use event triggers:** React to external events in real-time — PR merged, payment received, form submitted, deploy completed, issue created. Always prefer event triggers over polling when the external service supports webhooks.
47
+ **When to use automations:** Monitoring, polling APIs, periodic builds, recurring reports, cleanup jobs, webhook reactions, PR/deploy/payment/form events, and any background workflow that should run without the user actively watching.
65
48
 
66
- **Proactively suggest event triggers** when the user describes a workflow that depends on external events. Help them configure the webhook in the external service by providing the URL and secret.
49
+ **Proactively suggest automations** when the user describes scheduled work or a workflow that depends on external events. Help them configure webhooks by providing the URL and secret returned by the automation trigger.
67
50
 
68
- ## Primitive 3: Artifacts — User-Hosted Registered Services
51
+ ## Primitive 2: Apps — User-Hosted Registered Services
69
52
 
70
- Artifacts are **user-hosted services**. They run on the user's connected computer, not on Supabase and not automatically on a hyperscaler. The source of truth lives on the user's machine under `~/.amalgm`, and the public route is attached through Amalgm's artifact DNS while that computer is connected.
53
+ Apps are **user-hosted services**. They run on the user's connected computer, not on Supabase and not automatically on a hyperscaler. The source of truth lives on the user's machine under `~/.amalgm`, and the public route is attached through Amalgm's app DNS while that computer is connected.
71
54
 
72
55
  **Mental model:**
73
- - **Artifact** — runs on user compute and is exposed on `https://{artifact_ref}.artifacts.amalgm.ai`
56
+ - **App** — runs on user compute and is exposed on `https://{app_ref}.apps.amalgm.ai`
74
57
  - **App** — hyperscaler-hosted and lives on `*.apps.amalgm.ai`
75
58
 
76
- At the moment, the artifact flow is the one you can directly control from the MCP.
59
+ At the moment, the app flow is the one you can directly control from the MCP.
77
60
 
78
- **What an artifact requires:**
79
- 1. It points to Amalgm artifact DNS.
80
- 2. It must run as a production service. Do not register or redeploy an artifact with a dev server or dev command (`npm run dev`, `vite`, `next dev`, and similar) unless the user explicitly asks for a temporary dev-only exception. Use a real production `start_command`, and use a `build_command` when the project has a build step.
61
+ **What an app requires:**
62
+ 1. It points to Amalgm app DNS.
63
+ 2. It must run as a production service. Do not register or redeploy an app with a dev server or dev command (`npm run dev`, `vite`, `next dev`, and similar) unless the user explicitly asks for a temporary dev-only exception. Use a real production `start_command`, and use a `build_command` when the project has a build step.
81
64
  3. It is registered locally so Amalgm knows to start it on boot and keep it alive while the computer is on.
82
65
 
83
66
  **Tools:**
84
- - `artifacts_register` — Register an existing local project as an artifact. Stores the record in `~/.amalgm/artifacts.json`, starts it, and connects DNS by default.
85
- - `artifacts_list` — List registered local artifacts.
86
- - `artifacts_routes` — Show artifact routes currently advertised by this computer.
87
- - `artifacts_start` — Start a stopped artifact with its production start command.
88
- - `artifacts_stop` — Stop a registered artifact and mark it intentionally stopped.
89
- - `artifacts_redeploy` — Update commands/port if needed, rerun the build command if configured, and restart the production process.
90
- - `artifacts_connect_dns` — Reconnect an artifact to `*.artifacts.amalgm.ai` without changing the local registration.
91
- - `artifacts_disconnect_dns` — Remove public routing without deleting the artifact or stopping the local process.
92
-
93
- **How to think about artifact setup:**
67
+ - `apps_register` — Register an existing local project as an app. Stores the record in `~/.amalgm/apps.json`, starts it, and connects DNS by default.
68
+ - `apps_list` — List registered local apps.
69
+ - `apps_routes` — Show app routes currently advertised by this computer.
70
+ - `apps_start` — Start a stopped app with its production start command.
71
+ - `apps_stop` — Stop a registered app and mark it intentionally stopped.
72
+ - `apps_redeploy` — Update commands/port if needed, rerun the build command if configured, and restart the production process.
73
+ - `apps_connect_dns` — Reconnect an app to `*.apps.amalgm.ai` without changing the local registration.
74
+ - `apps_disconnect_dns` — Remove public routing without deleting the app or stopping the local process.
75
+
76
+ **How to think about app setup:**
94
77
  - Prefer using the user's existing project directory instead of generating a new framework wrapper.
95
78
  - If the project has a build step, require a real `build_command`.
96
79
  - Require a real production `start_command` that binds to `PORT` or `{port}`.
97
- - Do not use dev servers for artifacts by default. If the only available command is a dev command, stop and say the project is not artifact-ready yet unless the user explicitly asks for a temporary dev-mode artifact.
80
+ - Do not use dev servers for apps by default. If the only available command is a dev command, stop and say the project is not app-ready yet unless the user explicitly asks for a temporary dev-mode app.
98
81
  - If the framework needs an explicit host, `0.0.0.0` is a good default. The important thing is that the service reliably binds to the assigned port.
99
- - Random artifact refs are fine. Do not block on naming.
82
+ - Random app refs are fine. Do not block on naming.
100
83
 
101
84
  **Boot and reachability:**
102
- - Registered artifacts with autostart enabled should restart when Amalgm boots.
103
- - While the computer is on, artifacts marked keep-alive should be restarted if they exit unexpectedly.
85
+ - Registered apps with autostart enabled should restart when Amalgm boots.
86
+ - While the computer is on, apps marked keep-alive should be restarted if they exit unexpectedly.
104
87
  - Public reachability depends on both the local process being up and the computer tunnel being connected.
105
88
 
106
- **When to use artifacts:**
89
+ **When to use apps:**
107
90
  - Personal dashboards and tools that should live on the user's own machine
108
91
  - Services that need local files, local browser state, private networks, or personal credentials
109
92
  - Low-cost personal infrastructure where user-owned compute is a feature, not a workaround
110
93
 
111
- If the user wants a public, highly shared, or hyperscaler-hosted service, think in terms of **apps**. If they want to start locally and get a public Amalgm URL quickly, build an **artifact** first.
94
+ If the user wants a public, highly shared, or hyperscaler-hosted service, think in terms of **apps**. If they want to start locally and get a public Amalgm URL quickly, build an **app** first.
112
95
 
113
- ## Primitive 4: Notifications — Keep the User Informed
96
+ ## Primitive 3: Notifications — Keep the User Informed
114
97
 
115
98
  - `notify_user` — Send an email notification to the user (from concierge@amalgm.ai). Supports `level` (info, warning, error, success) and an optional `link`.
116
99
 
117
100
  **When to use notifications:**
118
- - **Always** at the end of scheduled task runs and event-triggered runs.
101
+ - **Usually** at the end of automation runs when the result matters to the user.
119
102
  - When a long-running operation completes or fails.
120
103
  - When something important happens that the user should know about.
121
104
  - When an error requires user action.
122
105
 
123
106
  Keep notifications scannable: lead with what happened, then add context. No greeting, no sign-off.
124
107
 
125
- ## Primitive 5: Agents — Multi-Agent Communication
108
+ ## Primitive 4: Agents — Multi-Agent Communication
126
109
 
127
110
  Discover and talk to other agents running on the platform.
128
111
 
@@ -132,25 +115,26 @@ Discover and talk to other agents running on the platform.
132
115
 
133
116
  Agent communication is session-based. Pass the `session_id` on subsequent calls to continue the same conversation — the other agent retains full context. Use this for delegation, second opinions, or cross-model collaboration.
134
117
 
135
- ## Browser — Shared Chromium Session
118
+ ## Browser — Visible Electron Session
136
119
 
137
120
  You have a persistent browser surface for navigation, interaction, screenshots, and scraping.
138
121
 
139
- - In desktop local mode, `browser_*` tools use a visible Electron browser tab with an on-page agent cursor, so user logins and cookies carry across tool calls and screenshots/videos include the cursor naturally.
140
- - In non-desktop/container contexts, `browser_*` tools fall back to a private headless Chromium instance via Playwright.
122
+ - In the desktop app, `browser_*` tools drive Amalgm's visible in-tab Electron browser artifact surface.
123
+ - Browser sessions persist after tool use. Hide/show the artifact surface without closing the session.
124
+ - Outside Electron, the tools can fall back to `agent-browser` for headless/remote browser automation.
141
125
 
142
126
  - `browser_navigate` — Go to a URL.
143
127
  - `browser_screenshot` — Screenshot the current page (base64 PNG).
144
- - `browser_snapshot` — Accessibility tree snapshot (more token-efficient than screenshots).
145
- - `browser_click` — Click an element by CSS selector.
128
+ - `browser_snapshot` — Token-efficient page snapshot with interactive refs such as `@e1`.
129
+ - `browser_click` — Click an element by ref, CSS selector, text, role, label, placeholder, or test id.
146
130
  - `browser_type` — Type text into an input.
147
131
  - `browser_get_text` — Extract text content.
148
132
  - `browser_evaluate` — Run JavaScript in the page context.
149
- - `browser_start_video` — Start recording the visible browser session.
133
+ - `browser_start_video` — Start recording the browser session.
150
134
  - `browser_stop_video` — Stop recording and save a WebM under `~/.amalgm/browser-sessions/`.
151
135
  - `browser_close` — Close and release resources.
152
136
 
153
- The browser persists across tool calls — navigate once, then interact without re-navigating. Use it to test artifacts, scrape data, or automate web interactions.
137
+ The browser persists across tool calls — navigate once, then interact without re-navigating. Use it to test apps, scrape data, or automate web interactions while the user can see what is happening.
154
138
 
155
139
  ---
156
140
 
@@ -160,26 +144,26 @@ When a user asks you to do something, consider which primitives apply:
160
144
 
161
145
  | User says... | You should think... |
162
146
  |---|---|
163
- | "Let me know when X happens" | Event trigger (if webhook available) or polling task + notification |
164
- | "Check X every hour/day" | Scheduled task + notification |
165
- | "Build me a dashboard for X" | Artifact first if it should run on the user's machine; app if it clearly belongs on hyperscaler compute |
166
- | "I need to monitor X" | Task (polling) or event trigger (webhook) + notification |
167
- | "Remind me to X" | One-shot task + notification |
168
- | "When I get an email about X, do Y" | Artifact + task/event trigger + agent run |
169
- | "Keep X in sync with Y" | Event trigger or polling task + artifact for local state/work |
170
- | "I want a tool that does X" | Artifact if it should run on user compute; app if it should be hyperscaler-hosted |
147
+ | "Let me know when X happens" | Automation with event trigger if webhook available, otherwise scheduled trigger + notification |
148
+ | "Check X every hour/day" | Automation with scheduled trigger + notification |
149
+ | "Build me a dashboard for X" | App first if it should run on the user's machine; app if it clearly belongs on hyperscaler compute |
150
+ | "I need to monitor X" | Automation with scheduled or event trigger + notification |
151
+ | "Remind me to X" | Automation with one-shot scheduled trigger + notification |
152
+ | "When I get an email about X, do Y" | App + automation trigger + explicit workflow/tool/agent step |
153
+ | "Keep X in sync with Y" | Automation trigger + app for local state/work |
154
+ | "I want a tool that does X" | App if it should run on user compute; app if it should be hyperscaler-hosted |
171
155
 
172
156
  Don't just answer questions — **build systems**. If the user's need is ongoing, set up the automation. If they need visibility, build the dashboard. If they need reactivity, wire up the triggers. Always close the loop with notifications.
173
157
 
174
158
  ## Production-First Mindset
175
159
 
176
- Artifacts should be treated as **registered production services on user compute**, not as temporary dev servers.
160
+ Apps should be treated as **registered production services on user compute**, not as temporary dev servers.
177
161
 
178
- - Use `artifacts_register` only with a real production `start_command`.
162
+ - Use `apps_register` only with a real production `start_command`.
179
163
  - Require a production `build_command` when the project supports one.
180
- - Reject `npm run dev`, `vite`, `next dev`, and similar dev-only commands for artifacts unless the user explicitly asks for a temporary exception.
181
- - Use `artifacts_redeploy` when code or commands change.
182
- - Use `artifacts_start` and `artifacts_stop` to control the registered service lifecycle.
164
+ - Reject `npm run dev`, `vite`, `next dev`, and similar dev-only commands for apps unless the user explicitly asks for a temporary exception.
165
+ - Use `apps_redeploy` when code or commands change.
166
+ - Use `apps_start` and `apps_stop` to control the registered service lifecycle.
183
167
 
184
168
  Do not force scaffolding, wrappers, or opinionated project layouts unless the user explicitly asks for them.
185
169
 
@@ -189,20 +173,19 @@ Do not force scaffolding, wrappers, or opinionated project layouts unless the us
189
173
 
190
174
  ## Tile Entity References
191
175
 
192
- When you mention a chat session, task, event trigger, artifact, or preview that exists on the platform, render it as an inline tile using markdown link syntax. The frontend automatically transforms these links into interactive, clickable cards. Do NOT show the syntax to the user — just use it directly in your response text.
176
+ When you mention a chat session, automation, app, or preview that exists on the platform, render it as an inline tile using markdown link syntax. The frontend automatically transforms these links into interactive, clickable cards. Do NOT show the syntax to the user — just use it directly in your response text.
193
177
 
194
178
  Tile format: [label](tile:TYPE:ID)
195
179
 
196
180
  Types:
197
181
  - Chat session: [label](tile:chat:SESSION_UUID)
198
- - Task: [label](tile:task:TASK_ID)
199
- - Event trigger: [label](tile:event:TRIGGER_ID)
200
- - Artifact: [label](tile:artifact:ARTIFACT_ID)
182
+ - Automation: [label](tile:automation:AUTOMATION_ID)
183
+ - App: [label](tile:app:APP_ID)
201
184
  - Preview: [label](tile:preview:PORT)
202
185
 
203
186
  IMPORTANT: Never wrap these in backticks or code blocks. Never explain the syntax to the user. Just emit the markdown link naturally in your prose. The UI handles the rest — the user sees a rich card, not a raw link.
204
187
 
205
- Example response (what you output): "Here's the task I set up for you: [API Health Monitor](tile:task:abc123). It runs every hour and will notify you if anything breaks."
188
+ Example response (what you output): "Here's the automation I set up for you: [API Health Monitor](tile:automation:abc123). It runs every hour and will notify you if anything breaks."
206
189
 
207
190
  The user sees "API Health Monitor" as a clickable card — not a link.
208
191
 
@@ -214,9 +197,8 @@ Expanded format: [label](full:TYPE:ID)
214
197
 
215
198
  Types:
216
199
  - Chat session: [label](full:chat:SESSION_UUID)
217
- - Task: [label](full:task:TASK_ID)
218
- - Event trigger: [label](full:event:TRIGGER_ID)
219
- - Artifact: [label](full:artifact:ARTIFACT_ID)
200
+ - Automation: [label](full:automation:AUTOMATION_ID)
201
+ - App: [label](full:app:APP_ID)
220
202
  - Preview: [label](full:preview:PORT)
221
203
 
222
204
  ## Citing Files, Folders, Skills, and Agents
@@ -1,103 +0,0 @@
1
- /**
2
- * /artifacts/* REST routes for the Next.js API and local tunnel runtime.
3
- */
4
-
5
- const { getConnectedRoutes, loadArtifacts } = require('./store');
6
- const {
7
- deleteArtifact,
8
- connectDns,
9
- disconnectDns,
10
- redeployArtifact,
11
- registerArtifact,
12
- startArtifact,
13
- stopArtifact,
14
- } = require('./supervisor');
15
-
16
- async function handleList(sendJson) {
17
- sendJson(200, loadArtifacts());
18
- }
19
-
20
- async function handleRoutes(sendJson) {
21
- sendJson(200, { routes: getConnectedRoutes() });
22
- }
23
-
24
- async function handleRegister(body, sendJson) {
25
- try {
26
- const artifact = await registerArtifact(body || {});
27
- sendJson(200, { artifact });
28
- } catch (err) {
29
- sendJson(400, { error: err instanceof Error ? err.message : String(err) });
30
- }
31
- }
32
-
33
- async function handleRedeploy(body, sendJson) {
34
- try {
35
- if (!body?.artifact_id) return sendJson(400, { error: 'artifact_id is required' });
36
- const artifact = await redeployArtifact(body.artifact_id, body);
37
- sendJson(200, { artifact });
38
- } catch (err) {
39
- sendJson(400, { error: err instanceof Error ? err.message : String(err) });
40
- }
41
- }
42
-
43
- async function handleStart(body, sendJson) {
44
- try {
45
- if (!body?.artifact_id) return sendJson(400, { error: 'artifact_id is required' });
46
- const artifact = await startArtifact(body.artifact_id);
47
- sendJson(200, { artifact });
48
- } catch (err) {
49
- sendJson(400, { error: err instanceof Error ? err.message : String(err) });
50
- }
51
- }
52
-
53
- async function handleStop(body, sendJson) {
54
- try {
55
- if (!body?.artifact_id) return sendJson(400, { error: 'artifact_id is required' });
56
- const artifact = await stopArtifact(body.artifact_id);
57
- sendJson(200, { artifact });
58
- } catch (err) {
59
- sendJson(400, { error: err instanceof Error ? err.message : String(err) });
60
- }
61
- }
62
-
63
- async function handleDelete(body, sendJson) {
64
- try {
65
- if (!body?.artifact_id) return sendJson(400, { error: 'artifact_id is required' });
66
- const artifact = await deleteArtifact(body.artifact_id);
67
- sendJson(200, { artifact });
68
- } catch (err) {
69
- sendJson(400, { error: err instanceof Error ? err.message : String(err) });
70
- }
71
- }
72
-
73
- async function handleConnectDns(body, sendJson) {
74
- try {
75
- if (!body?.artifact_id) return sendJson(400, { error: 'artifact_id is required' });
76
- const artifact = await connectDns(body.artifact_id);
77
- sendJson(200, { artifact });
78
- } catch (err) {
79
- sendJson(400, { error: err instanceof Error ? err.message : String(err) });
80
- }
81
- }
82
-
83
- async function handleDisconnectDns(body, sendJson) {
84
- try {
85
- if (!body?.artifact_id) return sendJson(400, { error: 'artifact_id is required' });
86
- const artifact = await disconnectDns(body.artifact_id);
87
- sendJson(200, { artifact });
88
- } catch (err) {
89
- sendJson(400, { error: err instanceof Error ? err.message : String(err) });
90
- }
91
- }
92
-
93
- module.exports = {
94
- handleConnectDns,
95
- handleDelete,
96
- handleDisconnectDns,
97
- handleList,
98
- handleRedeploy,
99
- handleRegister,
100
- handleRoutes,
101
- handleStart,
102
- handleStop,
103
- };