amalgm 0.1.88 → 0.1.89

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 (45) hide show
  1. package/lib/cli.js +16 -6
  2. package/lib/service.js +19 -0
  3. package/lib/supervisor.js +51 -0
  4. package/package.json +1 -1
  5. package/runtime/scripts/amalgm-mcp/apps/supervisor.js +0 -25
  6. package/runtime/scripts/amalgm-mcp/browser/auth-tools.js +179 -0
  7. package/runtime/scripts/amalgm-mcp/browser/cli.js +217 -0
  8. package/runtime/scripts/amalgm-mcp/browser/cua-tools.js +147 -0
  9. package/runtime/scripts/amalgm-mcp/browser/engine.js +454 -0
  10. package/runtime/scripts/amalgm-mcp/browser/recorder-tools.js +82 -0
  11. package/runtime/scripts/amalgm-mcp/browser/recorder.js +199 -0
  12. package/runtime/scripts/amalgm-mcp/browser/rest.js +5 -1
  13. package/runtime/scripts/amalgm-mcp/browser/tools.js +104 -731
  14. package/runtime/scripts/amalgm-mcp/fs/rest.js +5 -6
  15. package/runtime/scripts/amalgm-mcp/index.js +2 -0
  16. package/runtime/scripts/amalgm-mcp/lib/tool-result.js +32 -66
  17. package/runtime/scripts/amalgm-mcp/project-context/paths.js +266 -0
  18. package/runtime/scripts/amalgm-mcp/project-context/prompt.js +232 -0
  19. package/runtime/scripts/amalgm-mcp/project-context/rest.js +100 -0
  20. package/runtime/scripts/amalgm-mcp/project-context/store.js +671 -0
  21. package/runtime/scripts/amalgm-mcp/project-context/tools.js +146 -0
  22. package/runtime/scripts/amalgm-mcp/server/core-tools.js +35 -2
  23. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
  24. package/runtime/scripts/amalgm-mcp/server/routes/project-context.js +33 -0
  25. package/runtime/scripts/amalgm-mcp/state/db.js +11 -0
  26. package/runtime/scripts/amalgm-mcp/state/snapshot.js +3 -3
  27. package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +37 -4
  28. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +328 -0
  29. package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +9 -9
  30. package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +2 -2
  31. package/runtime/scripts/amalgm-mcp/toolbox/loadout-context.js +1 -0
  32. package/runtime/scripts/amalgm-mcp/toolbox/store.js +0 -15
  33. package/runtime/scripts/amalgm-mcp/toolbox/system-catalog.js +2 -0
  34. package/runtime/scripts/amalgm-mcp/workspace/rest.js +11 -7
  35. package/runtime/scripts/chat-core/adapters/opencode.js +1 -1
  36. package/runtime/scripts/chat-core/engine.js +16 -2
  37. package/runtime/scripts/chat-core/input.js +19 -1
  38. package/runtime/scripts/chat-core/tool-shape.js +6 -4
  39. package/runtime/scripts/chat-core/tooling/system-instructions.js +51 -0
  40. package/runtime/scripts/chat-core/tooling/system-prompt.js +34 -8
  41. package/runtime/scripts/local-gateway.js +1 -0
  42. package/runtime/scripts/amalgm-mcp/browser/agent-browser.js +0 -569
  43. package/runtime/scripts/amalgm-mcp/browser/page.js +0 -25
  44. package/runtime/scripts/chat-core/tooling/active-memory.js +0 -574
  45. package/runtime/scripts/chat-core/tooling/passive-memory.js +0 -576
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * memories — project memory tools (toolbox group `memories`).
5
+ *
6
+ * The model supplies content only. The runtime owns project resolution,
7
+ * timestamps, placement, revisions, the SQLite row, and Local Live events.
8
+ * Default project comes from the calling session's working directory; an
9
+ * explicit projectPath allows cross-project writes as long as the path lands
10
+ * inside a registered Amalgm project.
11
+ */
12
+
13
+ const { textResult, errorResult } = require('../lib/tool-result');
14
+ const store = require('./store');
15
+
16
+ function cleanString(value) {
17
+ return typeof value === 'string' && value.trim() ? value.trim() : '';
18
+ }
19
+
20
+ function targetPathFor(args, context) {
21
+ return cleanString(args?.projectPath)
22
+ || cleanString(context?.sessionMetadata?.cwd)
23
+ || cleanString(context?.sessionMetadata?.projectPath)
24
+ || '';
25
+ }
26
+
27
+ function noProjectError(targetPath) {
28
+ const where = targetPath ? ` for: ${targetPath}` : ' (no working directory on this session and no projectPath given)';
29
+ return errorResult(
30
+ `No registered Amalgm project found${where}. `
31
+ + 'Pass projectPath pointing inside a project that was added to Amalgm via the working-directory picker.',
32
+ );
33
+ }
34
+
35
+ const PROJECT_PATH_INPUT = {
36
+ type: 'string',
37
+ description:
38
+ 'Optional absolute path inside a registered Amalgm project. Defaults to the current session\'s project (its working directory). Use only for cross-project writes.',
39
+ };
40
+
41
+ module.exports = [
42
+ {
43
+ name: 'append_change_log',
44
+ description:
45
+ 'Append an entry to the active project\'s change log (change-log.md). Call this when durable, user-visible work is completed, a decision is made, or future agents need to know something. The runtime writes the timestamp (user\'s local time and timezone) and places the entry newest-first — submit ONLY the entry body.\n\nBody format: 3-6 concise, factual markdown bullets. Prefer:\n- Completed: short factual summary of what changed.\n- Decisions: durable choices made.\n- Notes: anything future agents should know.\n\nNo transcripts, no timestamps, no headings, no essays. Mark speculation explicitly as an open question.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ body: {
50
+ type: 'string',
51
+ description: 'Markdown body of the entry (bullets only, no timestamp heading).',
52
+ },
53
+ projectPath: PROJECT_PATH_INPUT,
54
+ },
55
+ required: ['body'],
56
+ },
57
+ async handler({ body, projectPath } = {}, context = {}) {
58
+ const target = targetPathFor({ projectPath }, context);
59
+ if (!target) return noProjectError('');
60
+ try {
61
+ const result = store.appendChangeLog(target, body, {
62
+ source: 'project-context:tool',
63
+ });
64
+ return textResult(
65
+ `Change log entry recorded for ${result.project.name} at ${result.timestamp.iso} [${result.timestamp.timeZone}].\n`
66
+ + `File: ${result.record?.paths?.changeLog || ''}`,
67
+ );
68
+ } catch (error) {
69
+ const message = error instanceof Error ? error.message : String(error);
70
+ if (message.startsWith('No registered Amalgm project')) return noProjectError(target);
71
+ return errorResult(message);
72
+ }
73
+ },
74
+ },
75
+ {
76
+ name: 'write_project_state',
77
+ description:
78
+ 'Rewrite the active project\'s consolidated state document (project-state.md). This is the "where the project stands now" snapshot injected into future sessions — use it to consolidate, not to log events (use append_change_log for history). Latest write wins; submit the COMPLETE new document as markdown. Keep it factual, current, and concise; remove stale information instead of appending.',
79
+ inputSchema: {
80
+ type: 'object',
81
+ properties: {
82
+ content: {
83
+ type: 'string',
84
+ description: 'Full markdown content of the new project-state.md (start with "# Project State").',
85
+ },
86
+ projectPath: PROJECT_PATH_INPUT,
87
+ },
88
+ required: ['content'],
89
+ },
90
+ async handler({ content, projectPath } = {}, context = {}) {
91
+ const target = targetPathFor({ projectPath }, context);
92
+ if (!target) return noProjectError('');
93
+ if (typeof content !== 'string' || !content.trim()) {
94
+ return errorResult('content is required (full markdown for project-state.md)');
95
+ }
96
+ try {
97
+ const result = store.writeProjectState(target, content, {
98
+ source: 'project-context:tool',
99
+ });
100
+ return textResult(
101
+ `Project state updated for ${result.project.name} (revision ${result.record?.files?.projectState?.revision || 'unknown'}).\n`
102
+ + `File: ${result.record?.paths?.projectState || ''}`,
103
+ );
104
+ } catch (error) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ if (message.startsWith('No registered Amalgm project')) return noProjectError(target);
107
+ return errorResult(message);
108
+ }
109
+ },
110
+ },
111
+ {
112
+ name: 'read_project_context',
113
+ description:
114
+ 'Read the active project\'s memory: instructions.md (user-authored), project-state.md (consolidated snapshot), and the recent change-log.md window (newest first). Use when you need to re-check project memory mid-session or inspect another project\'s context via projectPath.',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ projectPath: PROJECT_PATH_INPUT,
119
+ },
120
+ },
121
+ async handler({ projectPath } = {}, context = {}) {
122
+ const target = targetPathFor({ projectPath }, context);
123
+ if (!target) return noProjectError('');
124
+ try {
125
+ const record = store.getProjectContext(target, { source: 'project-context:tool-read' });
126
+ if (!record) return noProjectError(target);
127
+ const sections = [
128
+ `Project: ${record.name} (${record.projectPath})`,
129
+ `Context revision: ${record.contextRevision}`,
130
+ '',
131
+ '## instructions.md',
132
+ record.prompt?.instructions || '(empty)',
133
+ '',
134
+ '## project-state.md',
135
+ record.prompt?.projectState || '(empty)',
136
+ '',
137
+ '## change-log.md (recent)',
138
+ record.prompt?.changeLogRecent || '(empty)',
139
+ ];
140
+ return textResult(sections.join('\n'));
141
+ } catch (error) {
142
+ return errorResult(error instanceof Error ? error.message : String(error));
143
+ }
144
+ },
145
+ },
146
+ ];
@@ -8,7 +8,7 @@
8
8
  * circular dependency on the MCP server.
9
9
  */
10
10
 
11
- function group(id, name, description, tools) {
11
+ function group(id, name, description, tools, extra = {}) {
12
12
  return {
13
13
  id,
14
14
  name,
@@ -17,6 +17,7 @@ function group(id, name, description, tools) {
17
17
  origin: 'system',
18
18
  status: 'enabled',
19
19
  description,
20
+ ...extra,
20
21
  tools: tools.map((tool) => ({
21
22
  ...tool,
22
23
  toolboxToolId: id,
@@ -39,6 +40,12 @@ const CORE_TOOL_GROUPS = [
39
40
  'Notify the user about important results, completions, and failures.',
40
41
  require('../notify'),
41
42
  ),
43
+ group(
44
+ 'memories',
45
+ 'Memories',
46
+ 'Maintain project memory: append change-log entries and keep the project-state snapshot current.',
47
+ require('../project-context/tools'),
48
+ ),
42
49
  group(
43
50
  'agents',
44
51
  'Agents',
@@ -51,11 +58,37 @@ const CORE_TOOL_GROUPS = [
51
58
  'Register, run, and route local-first Amalgm apps.',
52
59
  require('../apps/tools'),
53
60
  ),
61
+ // The Browser family: one product primitive ("browser") plus optional
62
+ // capabilities that stay separately grantable but present as Browser modes,
63
+ // not peer products. `family`/`capability` flow into the toolbox catalog so
64
+ // UIs can nest them under Browser.
54
65
  group(
55
66
  'browser',
56
67
  'Browser',
57
- "Use Amalgm's visible browser session for navigation, interaction, and screenshots.",
68
+ "Drive Amalgm's browser: open pages, snapshot interactive elements, click, fill, and run any agent-browser CLI command.",
58
69
  require('../browser/tools'),
70
+ { family: 'browser', capability: 'core' },
71
+ ),
72
+ group(
73
+ 'browser_cua',
74
+ 'Browser Computer Use',
75
+ 'Browser capability: coordinate/pixel control for vision-model loops — screenshot, click and type at x/y.',
76
+ require('../browser/cua-tools'),
77
+ { family: 'browser', capability: 'computer-use' },
78
+ ),
79
+ group(
80
+ 'browser_recording',
81
+ 'Browser Recording',
82
+ 'Browser capability: record sessions to WebM videos stored with the Amalgm project they belong to.',
83
+ require('../browser/recorder-tools'),
84
+ { family: 'browser', capability: 'recording' },
85
+ ),
86
+ group(
87
+ 'browser_auth',
88
+ 'Browser Auth',
89
+ 'Browser infrastructure: durable profiles, encrypted auth bundles, and temporary login links.',
90
+ require('../browser/auth-tools'),
91
+ { family: 'browser', capability: 'auth' },
59
92
  ),
60
93
  ];
61
94
 
@@ -17,6 +17,7 @@ const { handleAgentRoutes } = require('./routes/agents');
17
17
  const { handleEventRoutes } = require('./routes/events');
18
18
  const { handleAutomationRoutes } = require('./routes/automations');
19
19
  const { handleWorkspaceRoutes } = require('./routes/workspace');
20
+ const { handleProjectContextRoutes } = require('./routes/project-context');
20
21
  const { handleFileRoutes } = require('./routes/files');
21
22
  const { handleToolboxRoutes } = require('./routes/toolbox');
22
23
  const { handleLocalRoutes } = require('./routes/local');
@@ -34,6 +35,7 @@ const DEFAULT_ROUTE_GROUPS = [
34
35
  handleEventRoutes,
35
36
  handleAutomationRoutes,
36
37
  handleWorkspaceRoutes,
38
+ handleProjectContextRoutes,
37
39
  handleFileRoutes,
38
40
  handleToolboxRoutes,
39
41
  handleLocalRoutes,
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const projectContextRest = require('../../project-context/rest');
4
+
5
+ async function handleProjectContextRoutes(ctx) {
6
+ if (ctx.pathname === '/project-context' && ctx.method === 'GET') {
7
+ await projectContextRest.handleGet(ctx.getQuery(), ctx.sendJson);
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/project-context/list' && ctx.method === 'GET') {
11
+ await projectContextRest.handleList(ctx.sendJson);
12
+ return true;
13
+ }
14
+ if (ctx.pathname === '/project-context/change-log' && ctx.method === 'POST') {
15
+ await projectContextRest.handleAppendChangeLog(await ctx.readJsonBody(), ctx.sendJson);
16
+ return true;
17
+ }
18
+ if (ctx.pathname === '/project-context/change-log' && ctx.method === 'PUT') {
19
+ await projectContextRest.handleWriteChangeLog(await ctx.readJsonBody(), ctx.sendJson);
20
+ return true;
21
+ }
22
+ if (ctx.pathname === '/project-context/project-state' && ctx.method === 'PUT') {
23
+ await projectContextRest.handleWriteProjectState(await ctx.readJsonBody(), ctx.sendJson);
24
+ return true;
25
+ }
26
+ if (ctx.pathname === '/project-context/instructions' && ctx.method === 'PUT') {
27
+ await projectContextRest.handleWriteInstructions(await ctx.readJsonBody(), ctx.sendJson);
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+
33
+ module.exports = { handleProjectContextRoutes };
@@ -319,6 +319,17 @@ function migrate(database = openLocalDb()) {
319
319
  CREATE INDEX IF NOT EXISTS agent_configs_updated_idx
320
320
  ON agent_configs(updated_at DESC);
321
321
 
322
+ CREATE TABLE IF NOT EXISTS project_context (
323
+ project_id TEXT PRIMARY KEY,
324
+ project_path TEXT NOT NULL,
325
+ context_revision TEXT NOT NULL,
326
+ updated_at TEXT NOT NULL,
327
+ context_json TEXT NOT NULL
328
+ );
329
+
330
+ CREATE INDEX IF NOT EXISTS project_context_path_idx
331
+ ON project_context(project_path);
332
+
322
333
  CREATE TABLE IF NOT EXISTS workspaces (
323
334
  id TEXT PRIMARY KEY,
324
335
  name TEXT NOT NULL,
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { currentSeq } = require('./events');
4
4
 
5
- const DEFAULT_RESOURCES = ['automations', 'triggers', 'workflows', 'automation_runs', 'workflow_cell_runs', 'agents', 'agent_configs', 'apps', 'toolbox', 'tools', 'tool_actions', 'hooks', 'projects', 'workspaces', 'memories', 'user_preferences', 'chat_payloads', 'mcp_connections', 'browser_profiles', 'browser_auth_bundles', 'browser_login_sessions'];
5
+ const DEFAULT_RESOURCES = ['automations', 'triggers', 'workflows', 'automation_runs', 'workflow_cell_runs', 'agents', 'agent_configs', 'apps', 'toolbox', 'tools', 'tool_actions', 'hooks', 'projects', 'workspaces', 'project_context', 'user_preferences', 'chat_payloads', 'mcp_connections', 'browser_profiles', 'browser_auth_bundles', 'browser_login_sessions'];
6
6
 
7
7
  function normalizeResources(resources) {
8
8
  const values = resources ? String(resources).split(',') : DEFAULT_RESOURCES;
@@ -59,8 +59,8 @@ function readResource(resource, cache) {
59
59
  const workspaceStore = require('../workspace/store');
60
60
  return workspaceStore.listWorkspaces();
61
61
  }
62
- case 'memories':
63
- return require('../../chat-core/tooling/active-memory').collectActiveMemoryFiles();
62
+ case 'project_context':
63
+ return require('../project-context/store').listProjectContexts();
64
64
  case 'user_preferences':
65
65
  return require('../lib/prefs').readUserPreferences();
66
66
  case 'chat_payloads':
@@ -9,9 +9,13 @@ test('core tools are grouped into first-party toolbox-style defaults', () => {
9
9
  assert.deepEqual(CORE_TOOL_GROUPS.map((group) => group.id), [
10
10
  'automations',
11
11
  'notifications',
12
+ 'memories',
12
13
  'agents',
13
14
  'apps',
14
15
  'browser',
16
+ 'browser_cua',
17
+ 'browser_recording',
18
+ 'browser_auth',
15
19
  ]);
16
20
  assert.equal(CORE_TOOL_GROUPS.every((group) => group.origin === 'system'), true);
17
21
  assert.equal(CORE_TOOL_GROUPS.every((group) => group.owner === 'amalgm'), true);
@@ -22,16 +26,45 @@ test('core tool grouping preserves handler definitions for MCP surface wrapping'
22
26
 
23
27
  assert.equal(CORE_TOOLS.length, groupedTools.length);
24
28
  assert.deepEqual(CORE_TOOLS.map((tool) => tool.name), groupedTools.map((tool) => tool.name));
25
- assert.equal(CORE_TOOLS.some((tool) => tool.name === 'browser_navigate'), true);
29
+ assert.equal(CORE_TOOLS.some((tool) => tool.name === 'browser_open'), true);
26
30
  assert.equal(CORE_TOOLS.some((tool) => tool.name === 'notify_user'), true);
31
+ assert.equal(CORE_TOOLS.some((tool) => tool.name === 'append_change_log'), true);
27
32
  });
28
33
 
29
34
  test('core tool actions carry stable toolbox ids', () => {
30
- const browserNavigate = CORE_TOOLS.find((tool) => tool.name === 'browser_navigate');
35
+ const browserOpen = CORE_TOOLS.find((tool) => tool.name === 'browser_open');
31
36
  const notifyUser = CORE_TOOLS.find((tool) => tool.name === 'notify_user');
32
37
 
33
- assert.equal(browserNavigate.toolboxToolId, 'browser');
34
- assert.equal(browserNavigate.toolboxActionId, 'browser.browser_navigate');
38
+ assert.equal(browserOpen.toolboxToolId, 'browser');
39
+ assert.equal(browserOpen.toolboxActionId, 'browser.browser_open');
35
40
  assert.equal(notifyUser.toolboxToolId, 'notifications');
36
41
  assert.equal(notifyUser.toolboxActionId, 'notifications.notify_user');
37
42
  });
43
+
44
+ test('browser surface stays thin and cua/recorder are separate groups', () => {
45
+ const browser = CORE_TOOL_GROUPS.find((group) => group.id === 'browser');
46
+ assert.deepEqual(browser.tools.map((tool) => tool.name), [
47
+ 'browser_open',
48
+ 'browser_snapshot',
49
+ 'browser_screenshot',
50
+ 'browser_click',
51
+ 'browser_fill',
52
+ 'browser_press',
53
+ 'browser_eval',
54
+ 'browser_wait',
55
+ 'browser_cli',
56
+ 'browser_close',
57
+ ]);
58
+
59
+ const cua = CORE_TOOL_GROUPS.find((group) => group.id === 'browser_cua');
60
+ assert.equal(cua.tools.every((tool) => tool.name.startsWith('cua_')), true);
61
+
62
+ const recorder = CORE_TOOL_GROUPS.find((group) => group.id === 'browser_recording');
63
+ assert.deepEqual(recorder.tools.map((tool) => tool.name), ['record_start', 'record_stop', 'record_list']);
64
+ });
65
+
66
+ test('browser capability groups carry family metadata for nested presentation', () => {
67
+ const family = CORE_TOOL_GROUPS.filter((group) => group.family === 'browser');
68
+ assert.deepEqual(family.map((group) => group.id), ['browser', 'browser_cua', 'browser_recording', 'browser_auth']);
69
+ assert.deepEqual(family.map((group) => group.capability), ['core', 'computer-use', 'recording', 'auth']);
70
+ });