@amodalai/runtime 0.3.49 → 0.3.50
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/dist/src/agent/completion-tools.integration.test.d.ts +6 -0
- package/dist/src/agent/completion-tools.integration.test.js +263 -0
- package/dist/src/agent/completion-tools.integration.test.js.map +1 -0
- package/dist/src/agent/load-template-plan.integration.test.d.ts +6 -0
- package/dist/src/agent/load-template-plan.integration.test.js +152 -0
- package/dist/src/agent/load-template-plan.integration.test.js.map +1 -0
- package/dist/src/agent/local-server.js +64 -9
- package/dist/src/agent/local-server.js.map +1 -1
- package/dist/src/agent/loop-types.d.ts +12 -2
- package/dist/src/agent/loop.test.js +5 -2
- package/dist/src/agent/loop.test.js.map +1 -1
- package/dist/src/agent/propose-plan.integration.test.d.ts +6 -0
- package/dist/src/agent/propose-plan.integration.test.js +186 -0
- package/dist/src/agent/propose-plan.integration.test.js.map +1 -0
- package/dist/src/agent/routes/package-updates.d.ts +42 -0
- package/dist/src/agent/routes/package-updates.js +207 -0
- package/dist/src/agent/routes/package-updates.js.map +1 -0
- package/dist/src/agent/routes/package-updates.test.d.ts +6 -0
- package/dist/src/agent/routes/package-updates.test.js +25 -0
- package/dist/src/agent/routes/package-updates.test.js.map +1 -0
- package/dist/src/agent/setup-state.integration.test.d.ts +6 -0
- package/dist/src/agent/setup-state.integration.test.js +182 -0
- package/dist/src/agent/setup-state.integration.test.js.map +1 -0
- package/dist/src/agent/snapshot-server.js +1 -0
- package/dist/src/agent/snapshot-server.js.map +1 -1
- package/dist/src/agent/states/executing.d.ts +6 -0
- package/dist/src/agent/states/executing.js +63 -27
- package/dist/src/agent/states/executing.js.map +1 -1
- package/dist/src/agent/states/streaming.js +18 -2
- package/dist/src/agent/states/streaming.js.map +1 -1
- package/dist/src/agent/tool-executor-local.js +11 -2
- package/dist/src/agent/tool-executor-local.js.map +1 -1
- package/dist/src/agent/validate-connection.integration.test.d.ts +6 -0
- package/dist/src/agent/validate-connection.integration.test.js +160 -0
- package/dist/src/agent/validate-connection.integration.test.js.map +1 -0
- package/dist/src/api/create-agent.js +1 -0
- package/dist/src/api/create-agent.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/intent/executor.d.ts +48 -0
- package/dist/src/intent/executor.js +420 -0
- package/dist/src/intent/executor.js.map +1 -0
- package/dist/src/intent/executor.test.d.ts +6 -0
- package/dist/src/intent/executor.test.js +543 -0
- package/dist/src/intent/executor.test.js.map +1 -0
- package/dist/src/intent/index.d.ts +10 -0
- package/dist/src/intent/index.js +9 -0
- package/dist/src/intent/index.js.map +1 -0
- package/dist/src/intent/loader.d.ts +16 -0
- package/dist/src/intent/loader.js +112 -0
- package/dist/src/intent/loader.js.map +1 -0
- package/dist/src/intent/loader.test.d.ts +6 -0
- package/dist/src/intent/loader.test.js +86 -0
- package/dist/src/intent/loader.test.js.map +1 -0
- package/dist/src/intent/matcher.d.ts +26 -0
- package/dist/src/intent/matcher.js +29 -0
- package/dist/src/intent/matcher.js.map +1 -0
- package/dist/src/intent/matcher.test.d.ts +6 -0
- package/dist/src/intent/matcher.test.js +53 -0
- package/dist/src/intent/matcher.test.js.map +1 -0
- package/dist/src/intent/onboarding.e2e.test.d.ts +6 -0
- package/dist/src/intent/onboarding.e2e.test.js +394 -0
- package/dist/src/intent/onboarding.e2e.test.js.map +1 -0
- package/dist/src/routes/ai-stream.js +96 -3
- package/dist/src/routes/ai-stream.js.map +1 -1
- package/dist/src/routes/session-resolver.js +16 -0
- package/dist/src/routes/session-resolver.js.map +1 -1
- package/dist/src/session/credential-scrubber.d.ts +35 -0
- package/dist/src/session/credential-scrubber.js +150 -0
- package/dist/src/session/credential-scrubber.js.map +1 -0
- package/dist/src/session/credential-scrubber.test.d.ts +6 -0
- package/dist/src/session/credential-scrubber.test.js +192 -0
- package/dist/src/session/credential-scrubber.test.js.map +1 -0
- package/dist/src/session/manager.intent.test.d.ts +6 -0
- package/dist/src/session/manager.intent.test.js +197 -0
- package/dist/src/session/manager.intent.test.js.map +1 -0
- package/dist/src/session/manager.js +114 -0
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/session-builder.d.ts +16 -1
- package/dist/src/session/session-builder.js +209 -41
- package/dist/src/session/session-builder.js.map +1 -1
- package/dist/src/session/store.js +48 -2
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/tool-context-factory.js +12 -0
- package/dist/src/session/tool-context-factory.js.map +1 -1
- package/dist/src/session/types.d.ts +12 -1
- package/dist/src/setup/commit-setup.d.ts +94 -0
- package/dist/src/setup/commit-setup.js +154 -0
- package/dist/src/setup/commit-setup.js.map +1 -0
- package/dist/src/setup/commit-setup.test.d.ts +6 -0
- package/dist/src/setup/commit-setup.test.js +310 -0
- package/dist/src/setup/commit-setup.test.js.map +1 -0
- package/dist/src/tools/README.md +270 -0
- package/dist/src/tools/admin-tools.d.ts +27 -0
- package/dist/src/tools/admin-tools.js +734 -0
- package/dist/src/tools/admin-tools.js.map +1 -0
- package/dist/src/tools/agent-package-discovery.test.d.ts +6 -0
- package/dist/src/tools/agent-package-discovery.test.js +90 -0
- package/dist/src/tools/agent-package-discovery.test.js.map +1 -0
- package/dist/src/tools/builtin/ask-choice.d.ts +8 -0
- package/dist/src/tools/builtin/ask-choice.js +54 -0
- package/dist/src/tools/builtin/ask-choice.js.map +1 -0
- package/dist/src/tools/context.d.ts +154 -0
- package/dist/src/tools/context.js +30 -0
- package/dist/src/tools/context.js.map +1 -0
- package/dist/src/tools/custom-tool-adapter.d.ts +33 -2
- package/dist/src/tools/custom-tool-adapter.js +38 -1
- package/dist/src/tools/custom-tool-adapter.js.map +1 -1
- package/dist/src/tools/custom-tool-adapter.test.js +48 -0
- package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
- package/dist/src/tools/fetch-url-tool.js +2 -0
- package/dist/src/tools/fetch-url-tool.js.map +1 -1
- package/dist/src/tools/file-tools.js +16 -0
- package/dist/src/tools/file-tools.js.map +1 -1
- package/dist/src/tools/fs/local.test.d.ts +6 -0
- package/dist/src/tools/fs/local.test.js +126 -0
- package/dist/src/tools/fs/local.test.js.map +1 -0
- package/dist/src/tools/index.d.ts +35 -0
- package/dist/src/tools/index.js +11 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/mcp-tool-adapter.js +2 -0
- package/dist/src/tools/mcp-tool-adapter.js.map +1 -1
- package/dist/src/tools/memory-tool.js +23 -1
- package/dist/src/tools/memory-tool.js.map +1 -1
- package/dist/src/tools/permissions.d.ts +36 -0
- package/dist/src/tools/permissions.js +97 -0
- package/dist/src/tools/permissions.js.map +1 -0
- package/dist/src/tools/permissions.test.d.ts +6 -0
- package/dist/src/tools/permissions.test.js +62 -0
- package/dist/src/tools/permissions.test.js.map +1 -0
- package/dist/src/tools/request-tool.js +2 -0
- package/dist/src/tools/request-tool.js.map +1 -1
- package/dist/src/tools/sdk-context.d.ts +43 -0
- package/dist/src/tools/sdk-context.js +94 -0
- package/dist/src/tools/sdk-context.js.map +1 -0
- package/dist/src/tools/sdk-context.test.d.ts +6 -0
- package/dist/src/tools/sdk-context.test.js +134 -0
- package/dist/src/tools/sdk-context.test.js.map +1 -0
- package/dist/src/tools/store-tools.js +6 -0
- package/dist/src/tools/store-tools.js.map +1 -1
- package/dist/src/tools/types.d.ts +53 -14
- package/dist/src/tools/web-search-tool.js +2 -0
- package/dist/src/tools/web-search-tool.js.map +1 -1
- package/dist/src/types.d.ts +164 -28
- package/dist/src/types.js +9 -3
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -4
- package/dist/src/tools/agent-config-tool.d.ts +0 -7
- package/dist/src/tools/agent-config-tool.js +0 -78
- package/dist/src/tools/agent-config-tool.js.map +0 -1
- package/dist/src/tools/collect-secret-tool.d.ts +0 -7
- package/dist/src/tools/collect-secret-tool.js +0 -47
- package/dist/src/tools/collect-secret-tool.js.map +0 -1
- package/dist/src/tools/fs/http.d.ts +0 -37
- package/dist/src/tools/fs/http.js +0 -88
- package/dist/src/tools/fs/http.js.map +0 -1
- package/dist/src/tools/http-file-tools.d.ts +0 -13
- package/dist/src/tools/http-file-tools.js +0 -146
- package/dist/src/tools/http-file-tools.js.map +0 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Phase D integration test for `propose_plan` + `update_plan`. Pins
|
|
8
|
+
* the agent-admin handlers end-to-end through the actual runtime
|
|
9
|
+
* path: LocalToolExecutor compiles handler.ts via esbuild,
|
|
10
|
+
* dynamic-imports the resulting .mjs, and runs each with a
|
|
11
|
+
* CustomToolContext whose `emit` callback captures SSE events the
|
|
12
|
+
* way the runtime's executing state would.
|
|
13
|
+
*
|
|
14
|
+
* Skips when the agent-admin cache (~/.amodal/admin-agent/latest/)
|
|
15
|
+
* isn't populated.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
21
|
+
import { LocalToolExecutor } from './tool-executor-local.js';
|
|
22
|
+
const ADMIN_AGENT_PATH = path.join(homedir(), '.amodal', 'admin-agent', 'latest');
|
|
23
|
+
const PROPOSE_HANDLER = path.join(ADMIN_AGENT_PATH, 'tools', 'propose_plan', 'handler.ts');
|
|
24
|
+
const UPDATE_HANDLER = path.join(ADMIN_AGENT_PATH, 'tools', 'update_plan', 'handler.ts');
|
|
25
|
+
const HAS_DEPS = existsSync(PROPOSE_HANDLER) && existsSync(UPDATE_HANDLER);
|
|
26
|
+
const describeWhenReady = HAS_DEPS ? describe : describe.skip;
|
|
27
|
+
describeWhenReady('propose_plan + update_plan (Phase D integration)', () => {
|
|
28
|
+
let executor;
|
|
29
|
+
let emitted;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
executor = new LocalToolExecutor();
|
|
32
|
+
emitted = [];
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
executor.dispose();
|
|
36
|
+
});
|
|
37
|
+
function loadedTool(handlerPath, name) {
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
description: `${name} test`,
|
|
41
|
+
parameters: {},
|
|
42
|
+
confirm: false,
|
|
43
|
+
timeout: 5_000,
|
|
44
|
+
env: [],
|
|
45
|
+
handlerPath,
|
|
46
|
+
location: path.dirname(handlerPath),
|
|
47
|
+
hasPackageJson: false,
|
|
48
|
+
hasSetupScript: false,
|
|
49
|
+
hasRequirementsTxt: false,
|
|
50
|
+
hasDockerfile: false,
|
|
51
|
+
sandboxLanguage: 'typescript',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function buildCtx() {
|
|
55
|
+
return {
|
|
56
|
+
async request() {
|
|
57
|
+
throw new Error('not used');
|
|
58
|
+
},
|
|
59
|
+
async exec() {
|
|
60
|
+
throw new Error('not used');
|
|
61
|
+
},
|
|
62
|
+
async store() {
|
|
63
|
+
throw new Error('not used');
|
|
64
|
+
},
|
|
65
|
+
env: () => undefined,
|
|
66
|
+
log: () => undefined,
|
|
67
|
+
signal: AbortSignal.timeout(30_000),
|
|
68
|
+
sessionId: 'sess_test',
|
|
69
|
+
emit: (event) => {
|
|
70
|
+
emitted.push(event);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
it('propose_plan emits a proposal SSE event with the right shape', async () => {
|
|
75
|
+
const result = await executor.execute(loadedTool(PROPOSE_HANDLER, 'propose_plan'), {
|
|
76
|
+
summary: 'Plumbing scheduler + reminders',
|
|
77
|
+
skills: [{ label: 'Job Scheduler', description: 'Daily schedule via chat' }],
|
|
78
|
+
requiredConnections: [{ label: 'Twilio', description: 'SMS reminders' }],
|
|
79
|
+
optionalConnections: [{ label: 'Google Calendar', description: 'Sync' }],
|
|
80
|
+
}, buildCtx());
|
|
81
|
+
expect(result).toMatchObject({ ok: true });
|
|
82
|
+
expect(emitted).toHaveLength(1);
|
|
83
|
+
const event = emitted[0];
|
|
84
|
+
expect(event.type).toBe('proposal');
|
|
85
|
+
if (event.type !== 'proposal')
|
|
86
|
+
throw new Error('unreachable');
|
|
87
|
+
expect(event.summary).toBe('Plumbing scheduler + reminders');
|
|
88
|
+
expect(event.skills[0]).toEqual({ label: 'Job Scheduler', description: 'Daily schedule via chat' });
|
|
89
|
+
expect(event.required_connections[0]?.label).toBe('Twilio');
|
|
90
|
+
expect(event.optional_connections[0]?.label).toBe('Google Calendar');
|
|
91
|
+
expect(event.proposal_id).toMatch(/^proposal_/);
|
|
92
|
+
});
|
|
93
|
+
it('propose_plan rejects empty summary with reason: invalid_params', async () => {
|
|
94
|
+
const result = await executor.execute(loadedTool(PROPOSE_HANDLER, 'propose_plan'), {
|
|
95
|
+
summary: '',
|
|
96
|
+
skills: [],
|
|
97
|
+
requiredConnections: [],
|
|
98
|
+
optionalConnections: [],
|
|
99
|
+
}, buildCtx());
|
|
100
|
+
expect(result).toMatchObject({ ok: false, reason: 'invalid_params' });
|
|
101
|
+
expect(emitted).toHaveLength(0);
|
|
102
|
+
});
|
|
103
|
+
it('propose_plan rejects malformed skill entries', async () => {
|
|
104
|
+
const result = await executor.execute(loadedTool(PROPOSE_HANDLER, 'propose_plan'), {
|
|
105
|
+
summary: 'x',
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional bad input
|
|
107
|
+
skills: [{ label: 'No description' }],
|
|
108
|
+
requiredConnections: [],
|
|
109
|
+
optionalConnections: [],
|
|
110
|
+
}, buildCtx());
|
|
111
|
+
expect(result).toMatchObject({ ok: false, reason: 'invalid_params' });
|
|
112
|
+
expect(emitted).toHaveLength(0);
|
|
113
|
+
});
|
|
114
|
+
it('propose_plan returns no_emit when ctx.emit is absent', async () => {
|
|
115
|
+
const ctxNoEmit = buildCtx();
|
|
116
|
+
delete ctxNoEmit.emit;
|
|
117
|
+
const result = await executor.execute(loadedTool(PROPOSE_HANDLER, 'propose_plan'), {
|
|
118
|
+
summary: 'x',
|
|
119
|
+
skills: [],
|
|
120
|
+
requiredConnections: [],
|
|
121
|
+
optionalConnections: [],
|
|
122
|
+
}, ctxNoEmit);
|
|
123
|
+
expect(result).toMatchObject({ ok: false, reason: 'no_emit' });
|
|
124
|
+
});
|
|
125
|
+
it('update_plan emits an update_plan SSE event with only the patched fields', async () => {
|
|
126
|
+
const result = await executor.execute(loadedTool(UPDATE_HANDLER, 'update_plan'), {
|
|
127
|
+
proposalId: 'proposal_abc',
|
|
128
|
+
optionalConnections: [{ label: 'QuickBooks', description: 'Invoicing' }],
|
|
129
|
+
}, buildCtx());
|
|
130
|
+
expect(result).toMatchObject({ ok: true, proposalId: 'proposal_abc' });
|
|
131
|
+
expect(emitted).toHaveLength(1);
|
|
132
|
+
const event = emitted[0];
|
|
133
|
+
expect(event.type).toBe('update_plan');
|
|
134
|
+
if (event.type !== 'update_plan')
|
|
135
|
+
throw new Error('unreachable');
|
|
136
|
+
expect(event.proposal_id).toBe('proposal_abc');
|
|
137
|
+
// Only patched fields are emitted; unspecified ones must be omitted
|
|
138
|
+
// so the widget reducer leaves them untouched.
|
|
139
|
+
expect(event.optional_connections).toEqual([{ label: 'QuickBooks', description: 'Invoicing' }]);
|
|
140
|
+
expect(event.skills).toBeUndefined();
|
|
141
|
+
expect(event.required_connections).toBeUndefined();
|
|
142
|
+
expect(event.summary).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
it('update_plan rejects empty proposalId', async () => {
|
|
145
|
+
const result = await executor.execute(loadedTool(UPDATE_HANDLER, 'update_plan'), { proposalId: '' }, buildCtx());
|
|
146
|
+
expect(result).toMatchObject({ ok: false, reason: 'invalid_params' });
|
|
147
|
+
});
|
|
148
|
+
it('update_plan accepts an empty array to clear a list', async () => {
|
|
149
|
+
const result = await executor.execute(loadedTool(UPDATE_HANDLER, 'update_plan'), { proposalId: 'p1', optionalConnections: [] }, buildCtx());
|
|
150
|
+
expect(result).toMatchObject({ ok: true });
|
|
151
|
+
const event = emitted[0];
|
|
152
|
+
if (event.type !== 'update_plan')
|
|
153
|
+
throw new Error('unreachable');
|
|
154
|
+
expect(event.optional_connections).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
it('propose_plan generates unique proposalIds across calls', async () => {
|
|
157
|
+
// Force time progression between calls so the suffix differs.
|
|
158
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
159
|
+
const r1 = await executor.execute(loadedTool(PROPOSE_HANDLER, 'propose_plan'), {
|
|
160
|
+
summary: 'a',
|
|
161
|
+
skills: [],
|
|
162
|
+
requiredConnections: [],
|
|
163
|
+
optionalConnections: [],
|
|
164
|
+
}, buildCtx());
|
|
165
|
+
await sleep(5);
|
|
166
|
+
const r2 = await executor.execute(loadedTool(PROPOSE_HANDLER, 'propose_plan'), {
|
|
167
|
+
summary: 'b',
|
|
168
|
+
skills: [],
|
|
169
|
+
requiredConnections: [],
|
|
170
|
+
optionalConnections: [],
|
|
171
|
+
}, buildCtx());
|
|
172
|
+
const id1 = r1.proposalId;
|
|
173
|
+
const id2 = r2.proposalId;
|
|
174
|
+
expect(id1).not.toBe(id2);
|
|
175
|
+
});
|
|
176
|
+
it('update_plan returns no_emit when ctx.emit is absent', async () => {
|
|
177
|
+
const ctxNoEmit = buildCtx();
|
|
178
|
+
delete ctxNoEmit.emit;
|
|
179
|
+
const result = await executor.execute(loadedTool(UPDATE_HANDLER, 'update_plan'), { proposalId: 'p1' }, ctxNoEmit);
|
|
180
|
+
expect(result).toMatchObject({ ok: false, reason: 'no_emit' });
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
// vi import is intentionally retained even when unused by individual cases:
|
|
184
|
+
// vitest's mock factory may be needed in the future for ctx variations.
|
|
185
|
+
void vi;
|
|
186
|
+
//# sourceMappingURL=propose-plan.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propose-plan.integration.test.js","sourceRoot":"","sources":["../../../src/agent/propose-plan.integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* GET /api/package-updates — checks every entry in `amodal.json#packages`
|
|
8
|
+
* for a newer published npm version and returns the diff. Cached in-memory
|
|
9
|
+
* for 1 day so refreshing the home screen doesn't hammer the registry.
|
|
10
|
+
*
|
|
11
|
+
* POST /api/package-updates/install — runs `npm install <pkg>@latest` in
|
|
12
|
+
* the repo root, then invalidates the cache so the next GET reflects the
|
|
13
|
+
* new state.
|
|
14
|
+
*/
|
|
15
|
+
import { type Router } from 'express';
|
|
16
|
+
import type { Logger } from '../../logger.js';
|
|
17
|
+
export interface PackageUpdate {
|
|
18
|
+
/** Package name, e.g. "@amodalai/connection-slack". */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Currently installed version (from node_modules/<pkg>/package.json). */
|
|
21
|
+
installed: string | null;
|
|
22
|
+
/** Latest version published to npm. Null if the registry call failed. */
|
|
23
|
+
latest: string | null;
|
|
24
|
+
/** True when latest > installed (string compare on semver-like values). */
|
|
25
|
+
hasUpdate: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface RouterDeps {
|
|
28
|
+
/** Repo root used to resolve `amodal.json` and `node_modules/<pkg>`. */
|
|
29
|
+
repoPath: string;
|
|
30
|
+
logger: Logger;
|
|
31
|
+
}
|
|
32
|
+
export declare function createPackageUpdatesRouter(deps: RouterDeps): Router;
|
|
33
|
+
declare function readAmodalPackages(repoPath: string): string[];
|
|
34
|
+
declare function readInstalledVersion(repoPath: string, name: string): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Loose semver compare: split on dots, compare numerically when possible.
|
|
37
|
+
* Returns negative when a < b, positive when a > b, 0 when equal. Tags like
|
|
38
|
+
* "1.2.0-beta.1" sort by lexical order on the tail — good enough for "is
|
|
39
|
+
* there an update available?".
|
|
40
|
+
*/
|
|
41
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
42
|
+
export { readAmodalPackages, readInstalledVersion };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* GET /api/package-updates — checks every entry in `amodal.json#packages`
|
|
8
|
+
* for a newer published npm version and returns the diff. Cached in-memory
|
|
9
|
+
* for 1 day so refreshing the home screen doesn't hammer the registry.
|
|
10
|
+
*
|
|
11
|
+
* POST /api/package-updates/install — runs `npm install <pkg>@latest` in
|
|
12
|
+
* the repo root, then invalidates the cache so the next GET reflects the
|
|
13
|
+
* new state.
|
|
14
|
+
*/
|
|
15
|
+
import express, {} from 'express';
|
|
16
|
+
import { execFile } from 'node:child_process';
|
|
17
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
18
|
+
import { promisify } from 'node:util';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 1 day
|
|
22
|
+
const NPM_VIEW_TIMEOUT_MS = 10_000;
|
|
23
|
+
const NPM_INSTALL_TIMEOUT_MS = 120_000;
|
|
24
|
+
const PACKAGE_NAME_REGEX = /^(?:@[a-z0-9-]+\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a path beneath `<repoPath>/node_modules` and verify the
|
|
27
|
+
* result stays inside that base. Returns null on traversal attempts
|
|
28
|
+
* (symlinks, double-resolved paths, etc.). The regex above already
|
|
29
|
+
* blocks `/` outside the scope segment, but CodeQL flags any flow
|
|
30
|
+
* from user input into a path expression — this is the belt and
|
|
31
|
+
* suspenders.
|
|
32
|
+
*/
|
|
33
|
+
function safeNodeModulesPath(repoPath, name, ...rest) {
|
|
34
|
+
const base = path.resolve(repoPath, 'node_modules');
|
|
35
|
+
const candidate = path.resolve(base, name, ...rest);
|
|
36
|
+
if (candidate !== base && !candidate.startsWith(base + path.sep))
|
|
37
|
+
return null;
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
export function createPackageUpdatesRouter(deps) {
|
|
41
|
+
const router = express.Router();
|
|
42
|
+
const cache = { payload: null };
|
|
43
|
+
router.get('/api/package-updates', (_req, res) => {
|
|
44
|
+
void (async () => {
|
|
45
|
+
try {
|
|
46
|
+
const fresh = cache.payload && Date.now() - cache.payload.checkedAt < CACHE_TTL_MS;
|
|
47
|
+
if (!fresh) {
|
|
48
|
+
cache.payload = await refreshUpdates(deps);
|
|
49
|
+
}
|
|
50
|
+
res.json(cache.payload);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
deps.logger.warn('package_updates_failed', { error: err instanceof Error ? err.message : String(err) });
|
|
54
|
+
res.status(500).json({ error: 'package_updates_failed' });
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
});
|
|
58
|
+
// Read the installed card.json for a single package — drives the
|
|
59
|
+
// "See what changed" page so it can show the user what they have today
|
|
60
|
+
// before they hit Update.
|
|
61
|
+
router.get('/api/package-card', (req, res) => {
|
|
62
|
+
void (async () => {
|
|
63
|
+
const name = typeof req.query['name'] === 'string' ? req.query['name'] : '';
|
|
64
|
+
if (!PACKAGE_NAME_REGEX.test(name)) {
|
|
65
|
+
res.status(400).json({ error: 'invalid_package_name' });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Allowlist gate: only read card.json for packages declared in
|
|
69
|
+
// amodal.json#packages. Belt to safeNodeModulesPath's suspenders —
|
|
70
|
+
// the regex above already blocks traversal characters, but explicit
|
|
71
|
+
// allowlisting tells static analyzers (CodeQL) that the eventual
|
|
72
|
+
// path expression depends on a sanitized value, not raw user input.
|
|
73
|
+
const declared = new Set(readAmodalPackages(deps.repoPath));
|
|
74
|
+
if (!declared.has(name)) {
|
|
75
|
+
res.status(404).json({ error: 'card_not_found' });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const cardPath = safeNodeModulesPath(deps.repoPath, name, 'card', 'card.json');
|
|
79
|
+
if (cardPath === null || !existsSync(cardPath)) {
|
|
80
|
+
res.status(404).json({ error: 'card_not_found' });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const raw = readFileSync(cardPath, 'utf-8');
|
|
85
|
+
res.type('application/json').send(raw);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
deps.logger.warn('package_card_read_failed', { name, error: err instanceof Error ? err.message : String(err) });
|
|
89
|
+
res.status(500).json({ error: 'card_read_failed' });
|
|
90
|
+
}
|
|
91
|
+
})();
|
|
92
|
+
});
|
|
93
|
+
router.post('/api/package-updates/install', express.json({ limit: '8kb' }), (req, res) => {
|
|
94
|
+
void (async () => {
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- parsing JSON body at module boundary
|
|
96
|
+
const body = req.body;
|
|
97
|
+
const name = body.name;
|
|
98
|
+
if (typeof name !== 'string' || !PACKAGE_NAME_REGEX.test(name)) {
|
|
99
|
+
res.status(400).json({ error: 'invalid_package_name' });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
await execFileAsync('npm', ['install', `${name}@latest`], {
|
|
104
|
+
cwd: deps.repoPath,
|
|
105
|
+
timeout: NPM_INSTALL_TIMEOUT_MS,
|
|
106
|
+
});
|
|
107
|
+
cache.payload = null; // force refresh on next GET
|
|
108
|
+
res.json({ ok: true, name });
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
112
|
+
deps.logger.warn('package_install_failed', { name, error: message });
|
|
113
|
+
res.status(500).json({ error: 'install_failed', message });
|
|
114
|
+
}
|
|
115
|
+
})();
|
|
116
|
+
});
|
|
117
|
+
return router;
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Refresh logic
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
async function refreshUpdates(deps) {
|
|
123
|
+
const { repoPath, logger } = deps;
|
|
124
|
+
const installedNames = readAmodalPackages(repoPath);
|
|
125
|
+
if (installedNames.length === 0) {
|
|
126
|
+
return { updates: [], checkedAt: Date.now() };
|
|
127
|
+
}
|
|
128
|
+
const updates = await Promise.all(installedNames.map(async (name) => {
|
|
129
|
+
const installed = readInstalledVersion(repoPath, name);
|
|
130
|
+
const latest = await fetchLatestVersion(name, logger);
|
|
131
|
+
const hasUpdate = installed !== null && latest !== null && compareVersions(installed, latest) < 0;
|
|
132
|
+
return { name, installed, latest, hasUpdate };
|
|
133
|
+
}));
|
|
134
|
+
return { updates, checkedAt: Date.now() };
|
|
135
|
+
}
|
|
136
|
+
function readAmodalPackages(repoPath) {
|
|
137
|
+
const configPath = path.join(repoPath, 'amodal.json');
|
|
138
|
+
if (!existsSync(configPath))
|
|
139
|
+
return [];
|
|
140
|
+
try {
|
|
141
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
142
|
+
if (typeof raw !== 'object' || raw === null)
|
|
143
|
+
return [];
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- guarded above
|
|
145
|
+
const packagesRaw = raw['packages'];
|
|
146
|
+
if (!Array.isArray(packagesRaw))
|
|
147
|
+
return [];
|
|
148
|
+
return packagesRaw.filter((n) => typeof n === 'string' && PACKAGE_NAME_REGEX.test(n));
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function readInstalledVersion(repoPath, name) {
|
|
155
|
+
const pkgPath = safeNodeModulesPath(repoPath, name, 'package.json');
|
|
156
|
+
if (pkgPath === null || !existsSync(pkgPath))
|
|
157
|
+
return null;
|
|
158
|
+
try {
|
|
159
|
+
const raw = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
160
|
+
if (typeof raw !== 'object' || raw === null)
|
|
161
|
+
return null;
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- guarded above
|
|
163
|
+
const version = raw['version'];
|
|
164
|
+
return typeof version === 'string' ? version : null;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function fetchLatestVersion(name, logger) {
|
|
171
|
+
try {
|
|
172
|
+
const { stdout } = await execFileAsync('npm', ['view', name, 'version'], {
|
|
173
|
+
timeout: NPM_VIEW_TIMEOUT_MS,
|
|
174
|
+
});
|
|
175
|
+
const version = stdout.trim();
|
|
176
|
+
return version === '' ? null : version;
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
logger.debug('npm_view_failed', { name, error: err instanceof Error ? err.message : String(err) });
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Loose semver compare: split on dots, compare numerically when possible.
|
|
185
|
+
* Returns negative when a < b, positive when a > b, 0 when equal. Tags like
|
|
186
|
+
* "1.2.0-beta.1" sort by lexical order on the tail — good enough for "is
|
|
187
|
+
* there an update available?".
|
|
188
|
+
*/
|
|
189
|
+
export function compareVersions(a, b) {
|
|
190
|
+
const partsA = a.split('.');
|
|
191
|
+
const partsB = b.split('.');
|
|
192
|
+
const len = Math.max(partsA.length, partsB.length);
|
|
193
|
+
for (let i = 0; i < len; i++) {
|
|
194
|
+
const av = partsA[i] ?? '0';
|
|
195
|
+
const bv = partsB[i] ?? '0';
|
|
196
|
+
const an = Number.parseInt(av, 10);
|
|
197
|
+
const bn = Number.parseInt(bv, 10);
|
|
198
|
+
if (!Number.isNaN(an) && !Number.isNaN(bn) && an !== bn)
|
|
199
|
+
return an - bn;
|
|
200
|
+
if (av !== bv)
|
|
201
|
+
return av < bv ? -1 : 1;
|
|
202
|
+
}
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
// Re-export for unit test access without going through the router.
|
|
206
|
+
export { readAmodalPackages, readInstalledVersion };
|
|
207
|
+
//# sourceMappingURL=package-updates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-updates.js","sourceRoot":"","sources":["../../../../src/agent/routes/package-updates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { compareVersions } from './package-updates.js';
|
|
8
|
+
describe('compareVersions', () => {
|
|
9
|
+
it('orders patch bumps numerically, not lexically', () => {
|
|
10
|
+
expect(compareVersions('1.2.10', '1.2.9')).toBeGreaterThan(0);
|
|
11
|
+
expect(compareVersions('1.2.9', '1.2.10')).toBeLessThan(0);
|
|
12
|
+
});
|
|
13
|
+
it('treats missing components as zero', () => {
|
|
14
|
+
expect(compareVersions('1.2', '1.2.0')).toBe(0);
|
|
15
|
+
expect(compareVersions('1.2', '1.2.1')).toBeLessThan(0);
|
|
16
|
+
});
|
|
17
|
+
it('returns 0 for identical versions', () => {
|
|
18
|
+
expect(compareVersions('0.3.40', '0.3.40')).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
it('orders prereleases lexically on the tail', () => {
|
|
21
|
+
expect(compareVersions('1.0.0-beta.1', '1.0.0-beta.2')).toBeLessThan(0);
|
|
22
|
+
expect(compareVersions('1.0.0-beta.10', '1.0.0-beta.9')).toBeGreaterThan(0);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=package-updates.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-updates.test.js","sourceRoot":"","sources":["../../../../src/agent/routes/package-updates.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAC,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAErD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChD,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,eAAe,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,eAAe,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|